Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:wolfi323:branches:KDE:Unstable:Applications
eventviews
_service:obs_scm:eventviews-VERSIONgit.20200713...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:obs_scm:eventviews-VERSIONgit.20200713T074025~752bb43.obscpio of Package eventviews
07070100000000000081A40000000200000002000000015F0BF3C900000802000000000000000000000000000000000000003D00000000eventviews-VERSIONgit.20200713T074025~752bb43/.gitlab-ci.ymlinclude: - https://invent.kde.org/sysadmin/ci-tooling/raw/master/invent/ci-before.yml - https://invent.kde.org/sysadmin/ci-tooling/raw/master/invent/ci-applications-linux.yml build_clazy_clang_tidy: stage: build image: kdeorg/ci-suse-qt514 extends: .linux #only: # - merge_requests before_script: - zypper install -y clazy - git clone --depth=1 https://invent.kde.org/sysadmin/ci-tooling.git $CI_TOOLING - git clone --depth=1 https://invent.kde.org/sysadmin/repo-metadata.git $CI_TOOLING/repo-metadata - git clone --depth=1 https://invent.kde.org/sysadmin/kde-build-metadata.git $CI_TOOLING/kde-build-metadata - git clone --depth=1 https://invent.kde.org/sdk/kde-dev-scripts.git $CI_TOOLING/kde-dev-scripts script: - export CXX=clazy - export CC=clang - export CXXFLAGS="-Wno-deprecated-declarations" - export CLAZY_HEADER_FILTER='^(?!ui_)\w+.h\$' - export CLAZY_CHECKS="level0,level1,detaching-member,ifndef-define-typo,isempty-vs-count,qrequiredresult-candidates,reserve-candidates,signal-with-return-value,unneeded-cast,function-args-by-ref,function-args-by-value,returning-void-expression,no-ctor-missing-parent-argument,isempty-vs-count,qhash-with-char-pointer-key,raw-environment-function,qproperty-type-mismatch,old-style-connect,qstring-allocations,container-inside-loop,heap-allocated-small-trivial-type,inefficient-qlist,qstring-varargs" - python3 -u $CI_TOOLING/helpers/prepare-dependencies.py --product $PRODUCT --project $PROJECT --branchGroup $BRANCH_GROUP --environment production --platform $PLATFORM --installTo $INSTALL_PREFIX - python3 -u $CI_TOOLING/helpers/configure-build.py --product $PRODUCT --project $PROJECT --branchGroup $BRANCH_GROUP --platform $PLATFORM --installTo $INSTALL_PREFIX - python3 -u $CI_TOOLING/helpers/compile-build.py --product $PRODUCT --project $PROJECT --branchGroup $BRANCH_GROUP --platform $PLATFORM --usingInstall $INSTALL_PREFIX - cd build && run-clang-tidy variables: PLATFORM: SUSEQt5.14 BRANCH_GROUP: kf5-qt5 07070100000001000081A40000000200000002000000015F0BF3C900000051000000000000000000000000000000000000003500000000eventviews-VERSIONgit.20200713T074025~752bb43/.krazyEXTRA camelcase,defines,kdebug,null,qenums,tipsandthis PRIORITY all STRICT all 07070100000002000081A40000000200000002000000015F0BF3C900000E32000000000000000000000000000000000000003D00000000eventviews-VERSIONgit.20200713T074025~752bb43/CMakeLists.txtcmake_minimum_required(VERSION 3.5) set(PIM_VERSION "5.15.40") project(eventviews VERSION ${PIM_VERSION}) # ECM setup set(KF5_MIN_VERSION "5.72.0") find_package(ECM ${KF5_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) set(LIBRARY_NAMELINK) if(POLICY CMP0053) cmake_policy(SET CMP0053 NEW) endif() include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(GenerateExportHeader) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMGeneratePriFile) include(FeatureSummary) include(ECMQtDeclareLoggingCategory) include(ECMAddTests) set(EVENTVIEW_LIB_VERSION ${PIM_VERSION}) set(CALENDARUTILS_LIB_VERSION "5.14.80") set(AKONADI_LIB_VERSION "5.14.80") set(QT_REQUIRED_VERSION "5.13.0") set(AKONADICALENDAR_LIB_VERSION "5.14.80") set(KMIME_LIB_VERSION "5.14.80") set(LIBKDEPIM_LIB_VERSION "5.14.80") set(CALENDARSUPPORT_LIB_VERSION "5.14.80") find_package(KF5Akonadi ${AKONADI_LIB_VERSION} CONFIG REQUIRED) find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets) find_package(KF5I18n ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Codecs ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5GuiAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5IconThemes ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Service ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Completion ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Holidays ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Contacts ${KF5_MIN_VERSION} CONFIG REQUIRED) set(KDIAGRAM_LIB_VERSION "1.4.0") find_package(KGantt ${KDIAGRAM_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Libkdepim ${LIBKDEPIM_LIB_VERSION} CONFIG REQUIRED) find_package(KF5CalendarUtils ${CALENDARUTILS_LIB_VERSION} CONFIG REQUIRED) find_package(KF5CalendarCore ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5CalendarSupport ${CALENDARSUPPORT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiCalendar ${AKONADICALENDAR_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Mime ${KMIME_LIB_VERSION} CONFIG REQUIRED) ecm_setup_version(PROJECT VARIABLE_PREFIX EVENTVIEWS VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/eventviews_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5EventViewsConfigVersion.cmake" SOVERSION 5 ) ########### Targets ########### add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050f00) add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054800) add_definitions(-DQT_NO_FOREACH) add_definitions(-DQT_NO_KEYWORDS) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) ########### CMake Config Files ########### set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5EventViews") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5EventViewsConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5EventViewsConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5EventViewsConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5EventViewsConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5EventViewsTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5EventViewsTargets.cmake NAMESPACE KF5::) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/eventviews_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) add_subdirectory(src) if(BUILD_TESTING) find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Test) add_subdirectory(tests) endif() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) 07070100000003000081A40000000200000002000000015F0BF3C900004761000000000000000000000000000000000000003600000000eventviews-VERSIONgit.20200713T074025~752bb43/COPYINGNOTE! The GPL below is copyrighted by the Free Software Foundation, but the instance of code that it refers to (the kde programs) are copyrighted by the authors who actually wrote it. --------------------------------------------------------------------------- GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. <one line to give the program's name and a brief idea of what it does.> Copyright (C) 19yy <name of author> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. <signature of Ty Coon>, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. 07070100000004000081A40000000200000002000000015F0BF3C90000679F000000000000000000000000000000000000003A00000000eventviews-VERSIONgit.20200713T074025~752bb43/COPYING.LIB GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. <one line to give the library's name and a brief idea of what it does.> Copyright (C) <year> <name of author> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. <signature of Ty Coon>, 1 April 1990 Ty Coon, President of Vice That's all there is to it! 07070100000005000081A40000000200000002000000015F0BF3C9000001A3000000000000000000000000000000000000004B00000000eventviews-VERSIONgit.20200713T074025~752bb43/KF5EventViewsConfig.cmake.in@PACKAGE_INIT@ include(CMakeFindDependencyMacro) find_dependency(KF5Akonadi "@AKONADI_LIB_VERSION@") find_dependency(KF5CalendarSupport "@CALENDARSUPPORT_LIB_VERSION@") find_dependency(KF5CalendarCore "@KF5_MIN_VERSION@") find_dependency(KF5CalendarUtils "@CALENDARUTILS_LIB_VERSION@") find_dependency(KF5AkonadiCalendar "@AKONADICALENDAR_LIB_VERSION@") include("${CMAKE_CURRENT_LIST_DIR}/KF5EventViewsTargets.cmake") 07070100000006000081A40000000200000002000000015F0BF3C9000000FC000000000000000000000000000000000000003C00000000eventviews-VERSIONgit.20200713T074025~752bb43/metainfo.yamldescription: Event views library tier: 3 type: functional platforms: - name: All portingAid: false deprecated: false release: false libraries: - qmake: EventViews cmake: "KF5::EventViews" cmakename: KF5EventViews group: kdepim public_lib: true 07070100000007000041ED00000002000000020000000B5F0BF3C900000000000000000000000000000000000000000000003200000000eventviews-VERSIONgit.20200713T074025~752bb43/src07070100000008000081A40000000200000002000000015F0BF3C90000178A000000000000000000000000000000000000004100000000eventviews-VERSIONgit.20200713T074025~752bb43/src/CMakeLists.txt# The following macros can be defined to alter behavior. # (if desired, use add_definitions() to define them) # # EVENTVIEWS_NODECOS - turns-off decoration plugins in views. # No idea why you would want to define this, perhaps to save # screen real estate? But there are a config options for that. add_definitions(-DTRANSLATION_DOMAIN=\"libeventviews\") if(KDEPIM_BUILD_EXAMPLES) add_subdirectory(viewerapp) endif() option(EVENTVIEWS_NODECOS "Turn-off decoration plugins in views." FALSE) if(EVENTVIEWS_NODECOS) add_definitions(-DEVENTVIEWS_NODECOS) endif() ########### next target ############### set(eventviews_LIB_SRCS eventview_p.cpp eventview.cpp helper.cpp prefs.cpp # Agenda view specific code. agenda/agenda.cpp agenda/agendaitem.cpp agenda/agendaview.cpp agenda/alternatelabel.cpp agenda/calendardecoration.cpp agenda/decorationlabel.cpp agenda/timelabels.cpp agenda/timelabelszone.cpp agenda/timescaleconfigdialog.cpp agenda/viewcalendar.cpp journal/journalframe.cpp journal/journalview.cpp list/listview.cpp month/monthgraphicsitems.cpp month/monthitem.cpp month/monthscene.cpp month/monthview.cpp multiagenda/multiagendaview.cpp todo/tododelegates.cpp todo/todomodel.cpp todo/incidencetreemodel.cpp todo/todoviewquickaddline.cpp todo/todoviewquicksearch.cpp todo/todoviewsortfilterproxymodel.cpp todo/todoviewview.cpp todo/todoview.cpp timeline/timelineview.cpp timeline/timelineitem.cpp timeline/timelineview_p.cpp whatsnext/whatsnextview.cpp ) kconfig_add_kcfg_files(eventviews_LIB_SRCS prefs_base.kcfgc) ecm_qt_declare_logging_category(eventviews_LIB_SRCS HEADER calendarview_debug.h IDENTIFIER CALENDARVIEW_LOG CATEGORY_NAME org.kde.pim.calendarview OLD_CATEGORY_NAMES log_calendarview DESCRIPTION "kdepim (eventviews)" EXPORT EVENTVIEWS) ki18n_wrap_ui(eventviews_LIB_SRCS agenda/timescaleedit_base.ui ) add_library(KF5EventViews ${eventviews_LIB_SRCS}) generate_export_header(KF5EventViews BASE_NAME eventviews) add_library(KF5::EventViews ALIAS KF5EventViews) target_link_libraries(KF5EventViews PUBLIC KF5::AkonadiCore KF5::CalendarSupport KF5::CalendarCore KF5::CalendarUtils KF5::AkonadiCalendar PRIVATE KF5::Holidays KF5::Completion KF5::Service KF5::IconThemes KF5::GuiAddons KF5::I18n KF5::Libkdepim KF5::Codecs KGantt KF5::AkonadiWidgets KF5::Contacts ) target_include_directories(KF5EventViews INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF5}/EventViews/;${KDE_INSTALL_INCLUDEDIR_KF5}/eventviews>") target_include_directories(KF5EventViews PUBLIC "$<BUILD_INTERFACE:${eventviews_SOURCE_DIR}/src;${eventviews_BINARY_DIR}/src;>") set_target_properties(KF5EventViews PROPERTIES VERSION ${EVENTVIEWS_VERSION_STRING} SOVERSION ${EVENTVIEWS_SOVERSION} EXPORT_NAME EventViews ) install(TARGETS KF5EventViews EXPORT KF5EventViewsTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK} ) ecm_generate_headers(eventviews_CamelCase_HEADERS HEADER_NAMES Helper Prefs EventView REQUIRED_HEADERS eventviews_CamelCase_HEADERS PREFIX EventViews ) ecm_generate_headers(eventviews_CamelCaseagenda_HEADERS HEADER_NAMES CalendarDecoration AgendaView ViewCalendar REQUIRED_HEADERS eventviews_agenda_HEADERS PREFIX EventViews RELATIVE agenda ) ecm_generate_headers(eventviews_CamelCasetodo_HEADERS HEADER_NAMES TodoView REQUIRED_HEADERS eventviews_todo_HEADERS PREFIX EventViews RELATIVE todo ) ecm_generate_headers(eventviews_CamelCasemonth_HEADERS HEADER_NAMES MonthView REQUIRED_HEADERS eventviews_month_HEADERS PREFIX EventViews RELATIVE month ) ecm_generate_headers(eventviews_CamelCaselist_HEADERS HEADER_NAMES ListView REQUIRED_HEADERS eventviews_list_HEADERS PREFIX EventViews RELATIVE list ) ecm_generate_headers(eventviews_CamelCasejournal_HEADERS HEADER_NAMES JournalView REQUIRED_HEADERS eventviews_journal_HEADERS PREFIX EventViews RELATIVE journal ) ecm_generate_headers(eventviews_CamelCasemultiagenda_HEADERS HEADER_NAMES MultiAgendaView ConfigDialogInterface REQUIRED_HEADERS eventviews_multiagenda_HEADERS PREFIX EventViews RELATIVE multiagenda ) ecm_generate_headers(eventviews_CamelCasetimeline_HEADERS HEADER_NAMES TimeLineView REQUIRED_HEADERS eventviews_timeline_HEADERS PREFIX EventViews RELATIVE timeline ) ecm_generate_headers(eventviews_CamelCasewhatsnext_HEADERS HEADER_NAMES WhatsNextView REQUIRED_HEADERS eventviews_whatsnext_HEADERS PREFIX EventViews RELATIVE whatsnext ) ecm_generate_pri_file(BASE_NAME EventViews LIB_NAME KF5EventViews DEPS "Mime AkonadiCalendar" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/EventViews ) install(FILES ${eventviews_CamelCase_HEADERS} ${eventviews_CamelCaseagenda_HEADERS} ${eventviews_CamelCasetodo_HEADERS} ${eventviews_CamelCasewhatsnext_HEADERS} ${eventviews_CamelCasetimeline_HEADERS} ${eventviews_CamelCasemultiagenda_HEADERS} ${eventviews_CamelCasejournal_HEADERS} ${eventviews_CamelCaselist_HEADERS} ${eventviews_CamelCasemonth_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/EventViews COMPONENT Devel ) install(FILES ${eventviews_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/eventviews_export.h ${eventviews_CamelCase_HEADERS} ${eventviews_agenda_HEADERS} ${eventviews_todo_HEADERS} ${eventviews_whatsnext_HEADERS} ${eventviews_timeline_HEADERS} ${eventviews_multiagenda_HEADERS} ${eventviews_journal_HEADERS} ${eventviews_list_HEADERS} ${eventviews_month_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/eventviews COMPONENT Devel ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) ecm_qt_install_logging_categories(EXPORT EVENTVIEWS FILE eventviews.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) install(FILES agenda/calendardecoration.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) 07070100000009000081A40000000200000002000000015F0BF3C9000000B2000000000000000000000000000000000000003E00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/Messages.sh#! /bin/sh $EXTRACTRC `find . -name '*.ui' -or -name '*.kcfg'` >> rc.cpp || exit 11 $XGETTEXT `find . -name "*.cpp" | grep -v '/tests/'` -o $podir/libeventviews.pot rm -f rc.cpp 0707010000000A000041ED0000000200000002000000025F0BF3C900000000000000000000000000000000000000000000003900000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda0707010000000B000081A40000000200000002000000015F0BF3C9000133B1000000000000000000000000000000000000004400000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/agenda.cpp/* Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com Author: Sergio Martins, sergio@kdab.com Marcus Bains line. Copyright (c) 2001 Ali Rahimi <ali@mit.edu> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "agenda.h" #include "agendaitem.h" #include "agendaview.h" #include "viewcalendar.h" #include "prefs.h" #include <Akonadi/Calendar/ETMCalendar> #include <Akonadi/Calendar/IncidenceChanger> #include <CalendarSupport/Utils> #include <KCalendarCore/Incidence> #include <KCalendarCore/Todo> #include <KCalUtils/RecurrenceActions> #include <KMessageBox> #include "calendarview_debug.h" #include <QApplication> #include <QLabel> #include <KLocalizedString> #include <QMouseEvent> #include <QPainter> #include <QPointer> #include <QResizeEvent> #include <QScrollBar> #include <QTimer> #include <QHash> #include <QWheelEvent> #include <QMultiHash> #include <cmath> // for fabs() using namespace EventViews; /////////////////////////////////////////////////////////////////////////////// class Q_DECL_HIDDEN MarcusBains::Private { public: Private(EventView *eventView, Agenda *agenda) : mEventView(eventView) , mAgenda(agenda) { } int todayColumn() const; public: EventView *mEventView = nullptr; Agenda *mAgenda = nullptr; QTimer *mTimer = nullptr; QLabel *mTimeBox = nullptr; // Label showing the current time QDateTime mOldDateTime; int mOldTodayCol = -1; }; int MarcusBains::Private::todayColumn() const { const QDate currentDate = QDate::currentDate(); int col = 0; const KCalendarCore::DateList dateList = mAgenda->dateList(); for (const QDate &date : dateList) { if (date == currentDate) { return QApplication::isRightToLeft() ? mAgenda->columns() - 1 - col : col; } ++col; } return -1; } MarcusBains::MarcusBains(EventView *eventView, Agenda *agenda) : QFrame(agenda) , d(new Private(eventView, agenda)) { d->mTimeBox = new QLabel(d->mAgenda); d->mTimeBox->setAlignment(Qt::AlignRight | Qt::AlignBottom); d->mTimer = new QTimer(this); d->mTimer->setSingleShot(true); connect(d->mTimer, &QTimer::timeout, this, &MarcusBains::updateLocation); d->mTimer->start(0); } MarcusBains::~MarcusBains() { delete d; } void MarcusBains::updateLocation() { updateLocationRecalc(); } void MarcusBains::updateLocationRecalc(bool recalculate) { const bool showSeconds = d->mEventView->preferences()->marcusBainsShowSeconds(); const QColor color = d->mEventView->preferences()->agendaMarcusBainsLineLineColor(); const QDateTime now = QDateTime::currentDateTime(); const QTime time = now.time(); if (now.date() != d->mOldDateTime.date()) { recalculate = true; // New day } const int todayCol = recalculate ? d->todayColumn() : d->mOldTodayCol; // Number of minutes since beginning of the day const int minutes = time.hour() * 60 + time.minute(); const int minutesPerCell = 24 * 60 / d->mAgenda->rows(); d->mOldDateTime = now; d->mOldTodayCol = todayCol; int y = int(minutes * d->mAgenda->gridSpacingY() / minutesPerCell); int x = int(d->mAgenda->gridSpacingX() * todayCol); bool hideIt = !(d->mEventView->preferences()->marcusBainsEnabled()); if (!isHidden() && (hideIt || (todayCol < 0))) { hide(); d->mTimeBox->hide(); return; } if (isHidden() && !hideIt) { show(); d->mTimeBox->show(); } /* Line */ // It seems logical to adjust the line width with the label's font weight const int fw = d->mEventView->preferences()->agendaMarcusBainsLineFont().weight(); setLineWidth(1 + abs(fw - QFont::Normal) / QFont::Light); setFrameStyle(QFrame::HLine | QFrame::Plain); QPalette pal = palette(); pal.setColor(QPalette::Window, color); // for Oxygen pal.setColor(QPalette::WindowText, color); // for Plastique setPalette(pal); if (recalculate) { setFixedSize(int(d->mAgenda->gridSpacingX()), 1); } move(x, y); raise(); /* Label */ d->mTimeBox->setFont(d->mEventView->preferences()->agendaMarcusBainsLineFont()); QPalette pal1 = d->mTimeBox->palette(); pal1.setColor(QPalette::WindowText, color); d->mTimeBox->setPalette(pal1); d->mTimeBox->setText(QLocale::system().toString(time, showSeconds ? QLocale::LongFormat : QLocale:: ShortFormat)); d->mTimeBox->adjustSize(); if (y - d->mTimeBox->height() >= 0) { y -= d->mTimeBox->height(); } else { y++; } if (x - d->mTimeBox->width() + d->mAgenda->gridSpacingX() > 0) { x += int(d->mAgenda->gridSpacingX() - d->mTimeBox->width() - 1); } else { x++; } d->mTimeBox->move(x, y); d->mTimeBox->raise(); if (showSeconds || recalculate) { d->mTimer->start(1000); } else { d->mTimer->start(1000 * (60 - time.second())); } } //////////////////////////////////////////////////////////////////////////// class Q_DECL_HIDDEN Agenda::Private { public: Private(AgendaView *agendaView, QScrollArea *scrollArea, int columns, int rows, int rowSize, bool isInteractive) : mAgendaView(agendaView) , mScrollArea(scrollArea) , mAllDayMode(false) , mColumns(columns) , mRows(rows) , mGridSpacingX(0.0) , mGridSpacingY(rowSize) , mDesiredGridSpacingY(rowSize) , mChanger(nullptr) , mResizeBorderWidth(0) , mScrollBorderWidth(0) , mScrollDelay(0) , mScrollOffset(0) , mWorkingHoursEnable(false) , mHolidayMask(nullptr) , mWorkingHoursYTop(0) , mWorkingHoursYBottom(0) , mHasSelection(0) , mSelectedId(-1) , mMarcusBains(nullptr) , mActionType(Agenda::NOP) , mItemMoved(false) , mOldLowerScrollValue(0) , mOldUpperScrollValue(0) , mReturnPressed(false) , mIsInteractive(isInteractive) { if (mGridSpacingY < 4 || mGridSpacingY > 30) { mGridSpacingY = 10; } } public: PrefsPtr preferences() const { return mAgendaView->preferences(); } bool isQueuedForDeletion(const QString &uid) const { // if mAgendaItemsById contains it it means that a createAgendaItem() was called // before the previous agenda items were deleted. return mItemsQueuedForDeletion.contains(uid) && !mAgendaItemsById.contains(uid); } QMultiHash<QString, AgendaItem::QPtr> mAgendaItemsById; // A QMultiHash because recurring incs // might have many agenda items QSet<QString> mItemsQueuedForDeletion; AgendaView *mAgendaView = nullptr; QScrollArea *mScrollArea = nullptr; bool mAllDayMode; // Number of Columns/Rows of agenda grid int mColumns; int mRows; // Width and height of agenda cells. mDesiredGridSpacingY is the height // set in the config. The actual height might be larger since otherwise // more than 24 hours might be displayed. double mGridSpacingX; double mGridSpacingY; double mDesiredGridSpacingY; Akonadi::IncidenceChanger *mChanger = nullptr; // size of border, where mouse action will resize the AgendaItem int mResizeBorderWidth; // size of border, where mouse mve will cause a scroll of the agenda int mScrollBorderWidth; int mScrollDelay; int mScrollOffset; QTimer mScrollUpTimer; QTimer mScrollDownTimer; // Cells to store Move and Resize coordiantes while performing the action QPoint mStartCell; QPoint mEndCell; // Working Hour coordiantes bool mWorkingHoursEnable; QVector<bool> *mHolidayMask = nullptr; int mWorkingHoursYTop; int mWorkingHoursYBottom; // Selection bool mHasSelection; QPoint mSelectionStartPoint; QPoint mSelectionStartCell; QPoint mSelectionEndCell; // List of dates to be displayed KCalendarCore::DateList mSelectedDates; // The AgendaItem, which has been right-clicked last QPointer<AgendaItem> mClickedItem; // The AgendaItem, which is being moved/resized QPointer<AgendaItem> mActionItem; // Currently selected item QPointer<AgendaItem> mSelectedItem; // Uid of the last selected incidence. Used for reselecting in situations // where the selected item points to a no longer valid incidence, for // example during resource reload. QString mSelectedId; // The Marcus Bains Line widget. MarcusBains *mMarcusBains = nullptr; MouseActionType mActionType; bool mItemMoved; // List of all Items contained in agenda QList<AgendaItem::QPtr> mItems; QList<AgendaItem::QPtr> mItemsToDelete; int mOldLowerScrollValue; int mOldUpperScrollValue; bool mReturnPressed; bool mIsInteractive; MultiViewCalendar::Ptr mCalendar; }; /* Create an agenda widget with rows rows and columns columns. */ Agenda::Agenda(AgendaView *agendaView, QScrollArea *scrollArea, int columns, int rows, int rowSize, bool isInteractive) : QWidget(scrollArea) , d(new Private(agendaView, scrollArea, columns, rows, rowSize, isInteractive)) { setMouseTracking(true); init(); } /* Create an agenda widget with columns columns and one row. This is used for all-day events. */ Agenda::Agenda(AgendaView *agendaView, QScrollArea *scrollArea, int columns, bool isInteractive) : QWidget(scrollArea) , d(new Private(agendaView, scrollArea, columns, 1, 24, isInteractive)) { d->mAllDayMode = true; init(); } Agenda::~Agenda() { delete d->mMarcusBains; delete d; } KCalendarCore::Incidence::Ptr Agenda::selectedIncidence() const { return d->mSelectedItem ? d->mSelectedItem->incidence() : KCalendarCore::Incidence::Ptr(); } QDate Agenda::selectedIncidenceDate() const { return d->mSelectedItem ? d->mSelectedItem->occurrenceDate() : QDate(); } QString Agenda::lastSelectedItemUid() const { return d->mSelectedId; } void Agenda::init() { setAttribute(Qt::WA_OpaquePaintEvent); d->mGridSpacingX = static_cast<double>(d->mScrollArea->width()) / d->mColumns; d->mDesiredGridSpacingY = d->preferences()->hourSize(); if (d->mDesiredGridSpacingY < 4 || d->mDesiredGridSpacingY > 30) { d->mDesiredGridSpacingY = 10; } // make sure that there are not more than 24 per day d->mGridSpacingY = static_cast<double>(height()) / d->mRows; if (d->mGridSpacingY < d->mDesiredGridSpacingY) { d->mGridSpacingY = d->mDesiredGridSpacingY; } d->mResizeBorderWidth = 12; d->mScrollBorderWidth = 12; d->mScrollDelay = 30; d->mScrollOffset = 10; // Grab key strokes for keyboard navigation of agenda. Seems to have no // effect. Has to be fixed. setFocusPolicy(Qt::WheelFocus); connect(&d->mScrollUpTimer, &QTimer::timeout, this, &Agenda::scrollUp); connect(&d->mScrollDownTimer, &QTimer::timeout, this, &Agenda::scrollDown); d->mStartCell = QPoint(0, 0); d->mEndCell = QPoint(0, 0); d->mHasSelection = false; d->mSelectionStartPoint = QPoint(0, 0); d->mSelectionStartCell = QPoint(0, 0); d->mSelectionEndCell = QPoint(0, 0); d->mOldLowerScrollValue = -1; d->mOldUpperScrollValue = -1; d->mClickedItem = nullptr; d->mActionItem = nullptr; d->mActionType = NOP; d->mItemMoved = false; d->mSelectedItem = nullptr; d->mSelectedId = -1; setAcceptDrops(true); installEventFilter(this); /* resizeContents(int(mGridSpacingX * mColumns), int(mGridSpacingY * mRows)); */ d->mScrollArea->viewport()->update(); // mScrollArea->viewport()->setAttribute(Qt::WA_NoSystemBackground, true); d->mScrollArea->viewport()->setFocusPolicy(Qt::WheelFocus); calculateWorkingHours(); connect(verticalScrollBar(), SIGNAL(valueChanged(int)), SLOT(checkScrollBoundaries(int))); // Create the Marcus Bains line. if (d->mAllDayMode) { d->mMarcusBains = nullptr; } else { d->mMarcusBains = new MarcusBains(d->mAgendaView, this); } } void Agenda::clear() { qDeleteAll(d->mItems); qDeleteAll(d->mItemsToDelete); d->mItems.clear(); d->mItemsToDelete.clear(); d->mAgendaItemsById.clear(); d->mItemsQueuedForDeletion.clear(); d->mSelectedItem = nullptr; clearSelection(); } void Agenda::clearSelection() { d->mHasSelection = false; d->mActionType = NOP; update(); } void Agenda::marcus_bains() { if (d->mMarcusBains) { d->mMarcusBains->updateLocationRecalc(true); } } void Agenda::changeColumns(int columns) { if (columns == 0) { qCDebug(CALENDARVIEW_LOG) << "called with argument 0"; return; } clear(); d->mColumns = columns; // setMinimumSize(mColumns * 10, mGridSpacingY + 1); // init(); // update(); QResizeEvent event(size(), size()); QApplication::sendEvent(this, &event); } int Agenda::columns() const { return d->mColumns; } int Agenda::rows() const { return d->mRows; } double Agenda::gridSpacingX() const { return d->mGridSpacingX; } double Agenda::gridSpacingY() const { return d->mGridSpacingY; } /* This is the eventFilter function, which gets all events from the AgendaItems contained in the agenda. It has to handle moving and resizing for all items. */ bool Agenda::eventFilter(QObject *object, QEvent *event) { switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: case QEvent::MouseButtonRelease: case QEvent::MouseMove: return eventFilter_mouse(object, static_cast<QMouseEvent *>(event)); #ifndef QT_NO_WHEELEVENT case QEvent::Wheel: return eventFilter_wheel(object, static_cast<QWheelEvent *>(event)); #endif case QEvent::KeyPress: case QEvent::KeyRelease: return eventFilter_key(object, static_cast<QKeyEvent *>(event)); case QEvent::Leave: #ifndef QT_NO_CURSOR if (!d->mActionItem) { setCursor(Qt::ArrowCursor); } #endif if (object == this) { // so timelabels hide the mouse cursor Q_EMIT leaveAgenda(); } return true; case QEvent::Enter: Q_EMIT enterAgenda(); return QWidget::eventFilter(object, event); #ifndef QT_NO_DRAGANDDROP case QEvent::DragEnter: case QEvent::DragMove: case QEvent::DragLeave: case QEvent::Drop: // case QEvent::DragResponse: return eventFilter_drag(object, static_cast<QDropEvent *>(event)); #endif default: return QWidget::eventFilter(object, event); } } bool Agenda::eventFilter_drag(QObject *obj, QDropEvent *de) { #ifndef QT_NO_DRAGANDDROP const QMimeData *md = de->mimeData(); switch (de->type()) { case QEvent::DragEnter: case QEvent::DragMove: if (!CalendarSupport::canDecode(md)) { return false; } if (CalendarSupport::mimeDataHasIncidence(md)) { de->accept(); } else { de->ignore(); } return true; break; case QEvent::DragLeave: return false; break; case QEvent::Drop: { if (!CalendarSupport::canDecode(md)) { return false; } const QList<QUrl> incidenceUrls = CalendarSupport::incidenceItemUrls(md); const KCalendarCore::Incidence::List incidences = CalendarSupport::incidences(md); Q_ASSERT(!incidenceUrls.isEmpty() || !incidences.isEmpty()); de->setDropAction(Qt::MoveAction); QWidget *dropTarget = qobject_cast<QWidget *>(obj); QPoint dropPosition = de->pos(); if (dropTarget && dropTarget != this) { dropPosition = dropTarget->mapTo(this, dropPosition); } const QPoint gridPosition = contentsToGrid(dropPosition); if (!incidenceUrls.isEmpty()) { Q_EMIT droppedIncidences(incidenceUrls, gridPosition, d->mAllDayMode); } else { Q_EMIT droppedIncidences(incidences, gridPosition, d->mAllDayMode); } return true; break; } case QEvent::DragResponse: default: break; } #endif return false; } #ifndef QT_NO_WHEELEVENT bool Agenda::eventFilter_wheel(QObject *object, QWheelEvent *e) { QPoint viewportPos; bool accepted = false; #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) const QPoint pos = e->pos(); #else const QPoint pos = e->position().toPoint(); #endif if ((e->modifiers() & Qt::ShiftModifier) == Qt::ShiftModifier) { if (object != this) { viewportPos = ((QWidget *)object)->mapToParent(pos); } else { viewportPos = pos; } //qCDebug(CALENDARVIEW_LOG) << type:" << e->type() << "angleDelta:" << e->angleDelta(); Q_EMIT zoomView(-e->angleDelta().y(), contentsToGrid(viewportPos), Qt::Horizontal); accepted = true; } if ((e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier) { if (object != this) { viewportPos = ((QWidget *)object)->mapToParent(pos); } else { viewportPos = pos; } Q_EMIT zoomView(-e->angleDelta().y(), contentsToGrid(viewportPos), Qt::Vertical); Q_EMIT mousePosSignal(gridToContents(contentsToGrid(viewportPos))); accepted = true; } if (accepted) { e->accept(); } return accepted; } #endif bool Agenda::eventFilter_key(QObject *, QKeyEvent *ke) { return d->mAgendaView->processKeyEvent(ke); } bool Agenda::eventFilter_mouse(QObject *object, QMouseEvent *me) { QPoint viewportPos; if (object != this) { viewportPos = static_cast<QWidget *>(object)->mapToParent(me->pos()); } else { viewportPos = me->pos(); } switch (me->type()) { case QEvent::MouseButtonPress: if (object != this) { if (me->button() == Qt::RightButton) { d->mClickedItem = qobject_cast<AgendaItem *>(object); if (d->mClickedItem) { selectItem(d->mClickedItem); Q_EMIT showIncidencePopupSignal(d->mClickedItem->incidence(), d->mClickedItem->occurrenceDate()); } } else { AgendaItem::QPtr item = qobject_cast<AgendaItem *>(object); if (item) { KCalendarCore::Incidence::Ptr incidence = item->incidence(); if (incidence->isReadOnly()) { d->mActionItem = nullptr; } else { d->mActionItem = item; startItemAction(viewportPos); } // Warning: do selectItem() as late as possible, since all // sorts of things happen during this call. Some can lead to // this filter being run again and mActionItem being set to // null. selectItem(item); } } } else { if (me->button() == Qt::RightButton) { // if mouse pointer is not in selection, select the cell below the cursor QPoint gpos = contentsToGrid(viewportPos); if (!ptInSelection(gpos)) { d->mSelectionStartCell = gpos; d->mSelectionEndCell = gpos; d->mHasSelection = true; Q_EMIT newStartSelectSignal(); Q_EMIT newTimeSpanSignal(d->mSelectionStartCell, d->mSelectionEndCell); // updateContents(); } Q_EMIT showNewEventPopupSignal(); } else { selectItem(nullptr); d->mActionItem = nullptr; #ifndef QT_NO_CURSOR setCursor(Qt::ArrowCursor); #endif startSelectAction(viewportPos); update(); } } break; case QEvent::MouseButtonRelease: if (d->mActionItem) { endItemAction(); } else if (d->mActionType == SELECT) { endSelectAction(viewportPos); } // This nasty gridToContents(contentsToGrid(..)) is needed to // avoid an offset of a few pixels. Don't ask me why... Q_EMIT mousePosSignal(gridToContents(contentsToGrid(viewportPos))); break; case QEvent::MouseMove: { if (!d->mIsInteractive) { return true; } // This nasty gridToContents(contentsToGrid(..)) is needed todos // avoid an offset of a few pixels. Don't ask me why... QPoint indicatorPos = gridToContents(contentsToGrid(viewportPos)); if (object != this) { AgendaItem::QPtr moveItem = qobject_cast<AgendaItem *>(object); KCalendarCore::Incidence::Ptr incidence = moveItem ? moveItem->incidence() : KCalendarCore::Incidence::Ptr(); if (incidence && !incidence->isReadOnly()) { if (!d->mActionItem) { setNoActionCursor(moveItem, viewportPos); } else { performItemAction(viewportPos); if (d->mActionType == MOVE) { // show cursor at the current begin of the item AgendaItem::QPtr firstItem = d->mActionItem->firstMultiItem(); if (!firstItem) { firstItem = d->mActionItem; } indicatorPos = gridToContents( QPoint(firstItem->cellXLeft(), firstItem->cellYTop())); } else if (d->mActionType == RESIZEBOTTOM) { // RESIZETOP is handled correctly, only resizebottom works differently indicatorPos = gridToContents( QPoint(d->mActionItem->cellXLeft(), d->mActionItem->cellYBottom() + 1)); } } // If we have an action item } // If move item && !read only } else { if (d->mActionType == SELECT) { performSelectAction(viewportPos); // show cursor at end of timespan if (((d->mStartCell.y() < d->mEndCell.y()) && (d->mEndCell.x() >= d->mStartCell.x())) || (d->mEndCell.x() > d->mStartCell.x())) { indicatorPos = gridToContents(QPoint(d->mEndCell.x(), d->mEndCell.y() + 1)); } else { indicatorPos = gridToContents(d->mEndCell); } } } Q_EMIT mousePosSignal(indicatorPos); break; } case QEvent::MouseButtonDblClick: if (object == this) { selectItem(nullptr); Q_EMIT newEventSignal(); } else { AgendaItem::QPtr doubleClickedItem = qobject_cast<AgendaItem *>(object); if (doubleClickedItem) { selectItem(doubleClickedItem); Q_EMIT editIncidenceSignal(doubleClickedItem->incidence()); } } break; default: break; } return true; } bool Agenda::ptInSelection(const QPoint &gpos) const { if (!d->mHasSelection) { return false; } else if (gpos.x() < d->mSelectionStartCell.x() || gpos.x() > d->mSelectionEndCell.x()) { return false; } else if ((gpos.x() == d->mSelectionStartCell.x()) && (gpos.y() < d->mSelectionStartCell.y())) { return false; } else if ((gpos.x() == d->mSelectionEndCell.x()) && (gpos.y() > d->mSelectionEndCell.y())) { return false; } return true; } void Agenda::startSelectAction(const QPoint &viewportPos) { Q_EMIT newStartSelectSignal(); d->mActionType = SELECT; d->mSelectionStartPoint = viewportPos; d->mHasSelection = true; QPoint pos = viewportPos; QPoint gpos = contentsToGrid(pos); // Store new selection d->mStartCell = gpos; d->mEndCell = gpos; d->mSelectionStartCell = gpos; d->mSelectionEndCell = gpos; // updateContents(); } void Agenda::performSelectAction(const QPoint &pos) { const QPoint gpos = contentsToGrid(pos); // Scroll if cursor was moved to upper or lower end of agenda. if (pos.y() - contentsY() < d->mScrollBorderWidth && contentsY() > 0) { d->mScrollUpTimer.start(d->mScrollDelay); } else if (contentsY() + d->mScrollArea->viewport()->height() - d->mScrollBorderWidth < pos.y()) { d->mScrollDownTimer.start(d->mScrollDelay); } else { d->mScrollUpTimer.stop(); d->mScrollDownTimer.stop(); } if (gpos != d->mEndCell) { d->mEndCell = gpos; if (d->mStartCell.x() > d->mEndCell.x() || (d->mStartCell.x() == d->mEndCell.x() && d->mStartCell.y() > d->mEndCell.y())) { // backward selection d->mSelectionStartCell = d->mEndCell; d->mSelectionEndCell = d->mStartCell; } else { d->mSelectionStartCell = d->mStartCell; d->mSelectionEndCell = d->mEndCell; } update(); } } void Agenda::endSelectAction(const QPoint ¤tPos) { d->mScrollUpTimer.stop(); d->mScrollDownTimer.stop(); d->mActionType = NOP; Q_EMIT newTimeSpanSignal(d->mSelectionStartCell, d->mSelectionEndCell); if (d->preferences()->selectionStartsEditor()) { if ((d->mSelectionStartPoint - currentPos).manhattanLength() > QApplication::startDragDistance()) { Q_EMIT newEventSignal(); } } } Agenda::MouseActionType Agenda::isInResizeArea(bool horizontal, const QPoint &pos, const AgendaItem::QPtr &item) { if (!item) { return NOP; } QPoint gridpos = contentsToGrid(pos); QPoint contpos = gridToContents( gridpos + QPoint((QApplication::isRightToLeft()) ? 1 : 0, 0)); if (horizontal) { int clXLeft = item->cellXLeft(); int clXRight = item->cellXRight(); if (QApplication::isRightToLeft()) { int tmp = clXLeft; clXLeft = clXRight; clXRight = tmp; } int gridDistanceX = int(pos.x() - contpos.x()); if (gridDistanceX < d->mResizeBorderWidth && clXLeft == gridpos.x()) { if (QApplication::isRightToLeft()) { return RESIZERIGHT; } else { return RESIZELEFT; } } else if ((d->mGridSpacingX - gridDistanceX) < d->mResizeBorderWidth && clXRight == gridpos.x()) { if (QApplication::isRightToLeft()) { return RESIZELEFT; } else { return RESIZERIGHT; } } else { return MOVE; } } else { int gridDistanceY = int(pos.y() - contpos.y()); if (gridDistanceY < d->mResizeBorderWidth && item->cellYTop() == gridpos.y() && !item->firstMultiItem()) { return RESIZETOP; } else if ((d->mGridSpacingY - gridDistanceY) < d->mResizeBorderWidth && item->cellYBottom() == gridpos.y() && !item->lastMultiItem()) { return RESIZEBOTTOM; } else { return MOVE; } } } void Agenda::startItemAction(const QPoint &pos) { Q_ASSERT(d->mActionItem); d->mStartCell = contentsToGrid(pos); d->mEndCell = d->mStartCell; bool noResize = CalendarSupport::hasTodo(d->mActionItem->incidence()); d->mActionType = MOVE; if (!noResize) { d->mActionType = isInResizeArea(d->mAllDayMode, pos, d->mActionItem); } d->mActionItem->startMove(); setActionCursor(d->mActionType, true); } void Agenda::performItemAction(const QPoint &pos) { QPoint gpos = contentsToGrid(pos); // Cursor left active agenda area. // This starts a drag. if (pos.y() < 0 || pos.y() >= contentsY() + d->mScrollArea->viewport()->height() || pos.x() < 0 || pos.x() >= width()) { if (d->mActionType == MOVE) { d->mScrollUpTimer.stop(); d->mScrollDownTimer.stop(); d->mActionItem->resetMove(); placeSubCells(d->mActionItem); Q_EMIT startDragSignal(d->mActionItem->incidence()); #ifndef QT_NO_CURSOR setCursor(Qt::ArrowCursor); #endif if (d->mChanger) { // d->mChanger->cancelChange(d->mActionItem->incidence()); } d->mActionItem = nullptr; d->mActionType = NOP; d->mItemMoved = false; return; } } else { setActionCursor(d->mActionType, true); } // Scroll if item was moved to upper or lower end of agenda. const int distanceToTop = pos.y() - contentsY(); if (distanceToTop < d->mScrollBorderWidth && distanceToTop > -d->mScrollBorderWidth) { d->mScrollUpTimer.start(d->mScrollDelay); } else if (contentsY() + d->mScrollArea->viewport()->height() - d->mScrollBorderWidth < pos.y()) { d->mScrollDownTimer.start(d->mScrollDelay); } else { d->mScrollUpTimer.stop(); d->mScrollDownTimer.stop(); } // Move or resize item if necessary if (d->mEndCell != gpos) { if (!d->mItemMoved) { if (!d->mChanger) { KMessageBox::information(this, i18n("Unable to lock item for modification. " "You cannot make any changes."), i18n("Locking Failed"), QStringLiteral("AgendaLockingFailed")); d->mScrollUpTimer.stop(); d->mScrollDownTimer.stop(); d->mActionItem->resetMove(); placeSubCells(d->mActionItem); #ifndef QT_NO_CURSOR setCursor(Qt::ArrowCursor); #endif d->mActionItem = nullptr; d->mActionType = NOP; d->mItemMoved = false; return; } d->mItemMoved = true; } d->mActionItem->raise(); if (d->mActionType == MOVE) { // Move all items belonging to a multi item AgendaItem::QPtr firstItem = d->mActionItem->firstMultiItem(); if (!firstItem) { firstItem = d->mActionItem; } AgendaItem::QPtr lastItem = d->mActionItem->lastMultiItem(); if (!lastItem) { lastItem = d->mActionItem; } QPoint deltapos = gpos - d->mEndCell; AgendaItem::QPtr moveItem = firstItem; while (moveItem) { bool changed = false; if (deltapos.x() != 0) { moveItem->moveRelative(deltapos.x(), 0); changed = true; } // in all day view don't try to move multi items, since there are none if (moveItem == firstItem && !d->mAllDayMode) { // is the first item int newY = deltapos.y() + moveItem->cellYTop(); // If event start moved earlier than 0:00, it starts the previous day if (newY < 0 && newY > d->mScrollBorderWidth) { moveItem->expandTop(-moveItem->cellYTop()); // prepend a new item at (x-1, rows()+newY to rows()) AgendaItem::QPtr newFirst = firstItem->prevMoveItem(); // cell's y values are first and last cell of the bar, // so if newY=-1, they need to be the same if (newFirst) { newFirst->setCellXY(moveItem->cellXLeft() - 1, rows() + newY, rows() - 1); d->mItems.append(newFirst); moveItem->resize(int(d->mGridSpacingX * newFirst->cellWidth()), int(d->mGridSpacingY * newFirst->cellHeight())); QPoint cpos = gridToContents( QPoint(newFirst->cellXLeft(), newFirst->cellYTop())); newFirst->setParent(this); newFirst->move(cpos.x(), cpos.y()); } else { newFirst = insertItem( moveItem->incidence(), moveItem->occurrenceDateTime(), moveItem->cellXLeft() - 1, rows() + newY, rows() - 1, moveItem->itemPos(), moveItem->itemCount(), false); } if (newFirst) { newFirst->show(); } moveItem->prependMoveItem(newFirst); firstItem = newFirst; } else if (newY >= rows()) { // If event start is moved past 24:00, it starts the next day // erase current item (i.e. remove it from the multiItem list) firstItem = moveItem->nextMultiItem(); moveItem->hide(); d->mItems.removeAll(moveItem); // removeChild(moveItem); d->mActionItem->removeMoveItem(moveItem); moveItem = firstItem; // adjust next day's item if (moveItem) { moveItem->expandTop(rows() - newY); } } else { moveItem->expandTop(deltapos.y(), true); } changed = true; } if (moveItem && !moveItem->lastMultiItem() && !d->mAllDayMode) { // is the last item int newY = deltapos.y() + moveItem->cellYBottom(); if (newY < 0) { // erase current item lastItem = moveItem->prevMultiItem(); moveItem->hide(); d->mItems.removeAll(moveItem); // removeChild(moveItem); moveItem->removeMoveItem(moveItem); moveItem = lastItem; moveItem->expandBottom(newY + 1); } else if (newY >= rows()) { moveItem->expandBottom(rows() - moveItem->cellYBottom() - 1); // append item at (x+1, 0 to newY-rows()) AgendaItem::QPtr newLast = lastItem->nextMoveItem(); if (newLast) { newLast->setCellXY(moveItem->cellXLeft() + 1, 0, newY - rows() - 1); d->mItems.append(newLast); moveItem->resize(int(d->mGridSpacingX * newLast->cellWidth()), int(d->mGridSpacingY * newLast->cellHeight())); QPoint cpos = gridToContents(QPoint(newLast->cellXLeft(), newLast->cellYTop())); newLast->setParent(this); newLast->move(cpos.x(), cpos.y()); } else { newLast = insertItem( moveItem->incidence(), moveItem->occurrenceDateTime(), moveItem->cellXLeft() + 1, 0, newY - rows() - 1, moveItem->itemPos(), moveItem->itemCount(), false); } moveItem->appendMoveItem(newLast); newLast->show(); lastItem = newLast; } else { moveItem->expandBottom(deltapos.y()); } changed = true; } if (changed) { adjustItemPosition(moveItem); } if (moveItem) { moveItem = moveItem->nextMultiItem(); } } } else if (d->mActionType == RESIZETOP) { if (d->mEndCell.y() <= d->mActionItem->cellYBottom()) { d->mActionItem->expandTop(gpos.y() - d->mEndCell.y()); adjustItemPosition(d->mActionItem); } } else if (d->mActionType == RESIZEBOTTOM) { if (d->mEndCell.y() >= d->mActionItem->cellYTop()) { d->mActionItem->expandBottom(gpos.y() - d->mEndCell.y()); adjustItemPosition(d->mActionItem); } } else if (d->mActionType == RESIZELEFT) { if (d->mEndCell.x() <= d->mActionItem->cellXRight()) { d->mActionItem->expandLeft(gpos.x() - d->mEndCell.x()); adjustItemPosition(d->mActionItem); } } else if (d->mActionType == RESIZERIGHT) { if (d->mEndCell.x() >= d->mActionItem->cellXLeft()) { d->mActionItem->expandRight(gpos.x() - d->mEndCell.x()); adjustItemPosition(d->mActionItem); } } d->mEndCell = gpos; } } void Agenda::endItemAction() { //PENDING(AKONADI_PORT) review all this cloning and changer calls d->mActionType = NOP; d->mScrollUpTimer.stop(); d->mScrollDownTimer.stop(); #ifndef QT_NO_CURSOR setCursor(Qt::ArrowCursor); #endif if (!d->mChanger) { qCCritical(CALENDARVIEW_LOG) << "No IncidenceChanger set"; return; } bool multiModify = false; // FIXME: do the cloning here... KCalendarCore::Incidence::Ptr incidence = d->mActionItem->incidence(); const auto recurrenceId = d->mActionItem->occurrenceDateTime(); d->mItemMoved = d->mItemMoved && !(d->mStartCell.x() == d->mEndCell.x() && d->mStartCell.y() == d->mEndCell.y()); bool addIncidence = false; if (d->mItemMoved) { bool modify = false; //get the main event and not the exception if (incidence->hasRecurrenceId() && !incidence->recurs()) { KCalendarCore::Incidence::Ptr mainIncidence; KCalendarCore::Calendar::Ptr cal = d->mCalendar->findCalendar(incidence)->getCalendar(); if (CalendarSupport::hasEvent(incidence)) { mainIncidence = cal->event(incidence->uid()); } else if (CalendarSupport::hasTodo(incidence)) { mainIncidence = cal->todo(incidence->uid()); } incidence = mainIncidence; } Akonadi::Item item = d->mCalendar->item(incidence); if (incidence->recurs()) { const int res = d->mAgendaView->showMoveRecurDialog(incidence, recurrenceId.date()); switch (res) { case KCalUtils::RecurrenceActions::AllOccurrences: // All occurrences // Moving the whole sequence of events is handled by the itemModified below. modify = true; break; case KCalUtils::RecurrenceActions::SelectedOccurrence: case KCalUtils::RecurrenceActions::FutureOccurrences: { const bool thisAndFuture = (res == KCalUtils::RecurrenceActions::FutureOccurrences); modify = true; multiModify = true; d->mChanger->startAtomicOperation(i18n("Dissociate event from recurrence")); KCalendarCore::Incidence::Ptr newInc(KCalendarCore::Calendar::createException( incidence, recurrenceId, thisAndFuture)); if (newInc) { newInc->removeCustomProperty("VOLATILE", "AKONADI-ID"); Akonadi::Item newItem = d->mCalendar->item(newInc); if (newItem.isValid() && newItem != item) { //it is not a new exception item = newItem; newInc->setCustomProperty("VOLATILE", "AKONADI-ID", QString::number(newItem.id())); addIncidence = false; } else { addIncidence = true; } // don't recreate items, they already have the correct position d->mAgendaView->enableAgendaUpdate(false); d->mActionItem->setIncidence(newInc); d->mActionItem->dissociateFromMultiItem(); d->mAgendaView->enableAgendaUpdate(true); } else { KMessageBox::sorry( this, i18n("Unable to add the exception item to the calendar. " "No change will be done."), i18n("Error Occurred")); } break; } default: modify = false; d->mActionItem->resetMove(); placeSubCells(d->mActionItem); //PENDING(AKONADI_PORT) should this be done after //the new item was asynchronously added? } } AgendaItem::QPtr placeItem = d->mActionItem->firstMultiItem(); if (!placeItem) { placeItem = d->mActionItem; } Akonadi::Collection::Id saveCollection = -1; if (item.isValid()) { saveCollection = item.parentCollection().id(); // if parent collection is only a search collection for example if (!(item.parentCollection().rights() & Akonadi::Collection::CanCreateItem)) { saveCollection = item.storageCollectionId(); } } if (modify) { d->mActionItem->endMove(); AgendaItem::QPtr modif = placeItem; QList<AgendaItem::QPtr> oldconflictItems = placeItem->conflictItems(); QList<AgendaItem::QPtr>::iterator it; for (it = oldconflictItems.begin(); it != oldconflictItems.end(); ++it) { if (*it) { placeSubCells(*it); } } while (placeItem) { placeSubCells(placeItem); placeItem = placeItem->nextMultiItem(); } // Notify about change // The agenda view will apply the changes to the actual Incidence*! // Bug #228696 don't call endChanged now it's async in Akonadi so it can // be called before that modified item was done. And endChange is // calling when we move item. // Not perfect need to improve it! //mChanger->endChange(inc); if (item.isValid()) { d->mAgendaView->updateEventDates(modif, addIncidence, saveCollection); } if (addIncidence) { // delete the one we dragged, there's a new one being added async, due to dissociation. delete modif; } } else { // the item was moved, but not further modified, since it's not recurring // make sure the view updates anyhow, with the right item if (item.isValid()) { d->mAgendaView->updateEventDates(placeItem, addIncidence, saveCollection); } } } d->mActionItem = nullptr; d->mItemMoved = false; if (multiModify) { d->mChanger->endAtomicOperation(); } } void Agenda::setActionCursor(int actionType, bool acting) { #ifndef QT_NO_CURSOR switch (actionType) { case MOVE: if (acting) { setCursor(Qt::SizeAllCursor); } else { setCursor(Qt::ArrowCursor); } break; case RESIZETOP: case RESIZEBOTTOM: setCursor(Qt::SizeVerCursor); break; case RESIZELEFT: case RESIZERIGHT: setCursor(Qt::SizeHorCursor); break; default: setCursor(Qt::ArrowCursor); } #endif } void Agenda::setNoActionCursor(const AgendaItem::QPtr &moveItem, const QPoint &pos) { const KCalendarCore::Incidence::Ptr item = moveItem ? moveItem->incidence() : KCalendarCore::Incidence::Ptr(); const bool noResize = CalendarSupport::hasTodo(item); Agenda::MouseActionType resizeType = MOVE; if (!noResize) { resizeType = isInResizeArea(d->mAllDayMode, pos, moveItem); } setActionCursor(resizeType); } /** calculate the width of the column subcells of the given item */ double Agenda::calcSubCellWidth(const AgendaItem::QPtr &item) { QPoint pt, pt1; pt = gridToContents(QPoint(item->cellXLeft(), item->cellYTop())); pt1 = gridToContents(QPoint(item->cellXLeft(), item->cellYTop()) + QPoint(1, 1)); pt1 -= pt; int maxSubCells = item->subCells(); double newSubCellWidth; if (d->mAllDayMode) { newSubCellWidth = static_cast<double>(pt1.y()) / maxSubCells; } else { newSubCellWidth = static_cast<double>(pt1.x()) / maxSubCells; } return newSubCellWidth; } void Agenda::adjustItemPosition(const AgendaItem::QPtr &item) { if (!item) { return; } item->resize(int(d->mGridSpacingX * item->cellWidth()), int(d->mGridSpacingY * item->cellHeight())); int clXLeft = item->cellXLeft(); if (QApplication::isRightToLeft()) { clXLeft = item->cellXRight() + 1; } QPoint cpos = gridToContents(QPoint(clXLeft, item->cellYTop())); item->move(cpos.x(), cpos.y()); } void Agenda::placeAgendaItem(const AgendaItem::QPtr &item, double subCellWidth) { // "left" upper corner, no subcells yet, RTL layouts have right/left // switched, widths are negative then QPoint pt = gridToContents(QPoint(item->cellXLeft(), item->cellYTop())); // right lower corner QPoint pt1 = gridToContents( QPoint(item->cellXLeft() + item->cellWidth(), item->cellYBottom() + 1)); double subCellPos = item->subCell() * subCellWidth; // we need to add 0.01 to make sure we don't loose one pixed due to numerics // (i.e. if it would be x.9998, we want the integer, not rounded down. double delta = 0.01; if (subCellWidth < 0) { delta = -delta; } int height, width, xpos, ypos; if (d->mAllDayMode) { width = pt1.x() - pt.x(); height = int(subCellPos + subCellWidth + delta) - int(subCellPos); xpos = pt.x(); ypos = pt.y() + int(subCellPos); } else { width = int(subCellPos + subCellWidth + delta) - int(subCellPos); height = pt1.y() - pt.y(); xpos = pt.x() + int(subCellPos); ypos = pt.y(); } if (QApplication::isRightToLeft()) { // RTL language/layout xpos += width; width = -width; } if (height < 0) { // BTT (bottom-to-top) layout ?!? ypos += height; height = -height; } item->resize(width, height); item->move(xpos, ypos); } /* Place item in cell and take care that multiple items using the same cell do not overlap. This method is not yet optimal. It doesn't use the maximum space it can get in all cases. At the moment the method has a bug: When an item is placed only the sub cell widths of the items are changed, which are within the Y region the item to place spans. When the sub cell width change of one of this items affects a cell, where other items are, which do not overlap in Y with the item to place, the display gets corrupted, although the corruption looks quite nice. */ void Agenda::placeSubCells(const AgendaItem::QPtr &placeItem) { #if 0 qCDebug(CALENDARVIEW_LOG); if (placeItem) { KCalendarCore::Incidence::Ptr event = placeItem->incidence(); if (!event) { qCDebug(CALENDARVIEW_LOG) << " event is 0"; } else { qCDebug(CALENDARVIEW_LOG) << " event:" << event->summary(); } } else { qCDebug(CALENDARVIEW_LOG) << " placeItem is 0"; } qCDebug(CALENDARVIEW_LOG) << "Agenda::placeSubCells()..."; #endif QList<CalendarSupport::CellItem *> cells; for (CalendarSupport::CellItem *item : qAsConst(d->mItems)) { if (item) { cells.append(item); } } QList<CalendarSupport::CellItem *> items = CalendarSupport::CellItem::placeItem(cells, placeItem); placeItem->setConflictItems(QList<AgendaItem::QPtr>()); double newSubCellWidth = calcSubCellWidth(placeItem); QList<CalendarSupport::CellItem *>::iterator it; for (it = items.begin(); it != items.end(); ++it) { if (*it) { AgendaItem::QPtr item = static_cast<AgendaItem *>(*it); placeAgendaItem(item, newSubCellWidth); item->addConflictItem(placeItem); placeItem->addConflictItem(item); } } if (items.isEmpty()) { placeAgendaItem(placeItem, newSubCellWidth); } placeItem->update(); } int Agenda::columnWidth(int column) const { int start = gridToContents(QPoint(column, 0)).x(); if (QApplication::isRightToLeft()) { column--; } else { column++; } int end = gridToContents(QPoint(column, 0)).x(); return end - start; } void Agenda::paintEvent(QPaintEvent *) { QPainter p(this); drawContents(&p, 0, -y(), d->mGridSpacingX * d->mColumns, d->mGridSpacingY * d->mRows + y()); } /* Draw grid in the background of the agenda. */ void Agenda::drawContents(QPainter *p, int cx, int cy, int cw, int ch) { QPixmap db(cw, ch); db.fill(); // We don't want to see leftovers from previous paints QPainter dbp(&db); // TODO: CHECK THIS // if (! d->preferences()->agendaGridBackgroundImage().isEmpty()) { // QPixmap bgImage(d->preferences()->agendaGridBackgroundImage()); // dbp.drawPixmap(0, 0, cw, ch, bgImage); FIXME // } if (!d->preferences()->useSystemColor()) { dbp.fillRect(0, 0, cw, ch, d->preferences()->agendaGridBackgroundColor()); } else { dbp.fillRect(0, 0, cw, ch, palette().color(QPalette::Window)); } dbp.translate(-cx, -cy); double lGridSpacingY = d->mGridSpacingY * 2; // If work day, use work color // If busy day, use busy color // if work and busy day, mix both, and busy color has alpha const QVector<bool> busyDayMask = d->mAgendaView->busyDayMask(); // Highlight working hours if (d->mWorkingHoursEnable && d->mHolidayMask) { QColor workColor; if (!d->preferences()->useSystemColor()) { workColor = d->preferences()->workingHoursColor(); } else { workColor = palette().color(QPalette::Base); } QPoint pt1(cx, d->mWorkingHoursYTop); QPoint pt2(cx + cw, d->mWorkingHoursYBottom); if (pt2.x() >= pt1.x() /*&& pt2.y() >= pt1.y()*/) { int gxStart = contentsToGrid(pt1).x(); int gxEnd = contentsToGrid(pt2).x(); // correct start/end for rtl layouts if (gxStart > gxEnd) { int tmp = gxStart; gxStart = gxEnd; gxEnd = tmp; } int xoffset = (QApplication::isRightToLeft() ? 1 : 0); while (gxStart <= gxEnd) { int xStart = gridToContents(QPoint(gxStart + xoffset, 0)).x(); int xWidth = columnWidth(gxStart) + 1; if (pt2.y() < pt1.y()) { // overnight working hours if (((gxStart == 0) && !d->mHolidayMask->at(d->mHolidayMask->count() - 1)) || ((gxStart > 0) && (gxStart < int(d->mHolidayMask->count())) && (!d->mHolidayMask->at(gxStart - 1)))) { if (pt2.y() > cy) { dbp.fillRect(xStart, cy, xWidth, pt2.y() - cy + 1, workColor); } } if ((gxStart < int(d->mHolidayMask->count() - 1)) && (!d->mHolidayMask->at(gxStart))) { if (pt1.y() < cy + ch - 1) { dbp.fillRect(xStart, pt1.y(), xWidth, cy + ch - pt1.y() + 1, workColor); } } } else { // last entry in holiday mask denotes the previous day not visible // (needed for overnight shifts) if (gxStart < int(d->mHolidayMask->count() - 1) && !d->mHolidayMask->at(gxStart)) { dbp.fillRect(xStart, pt1.y(), xWidth, pt2.y() - pt1.y() + 1, workColor); } } ++gxStart; } } } // busy days if (d->preferences()->colorAgendaBusyDays() && !d->mAllDayMode) { for (int i = 0; i < busyDayMask.count(); ++i) { if (busyDayMask[i]) { const QPoint pt1(cx + d->mGridSpacingX *i, 0); // const QPoint pt2(cx + mGridSpacingX * (i+1), ch); QColor busyColor; if (!d->preferences()->useSystemColor()) { busyColor = d->preferences()->viewBgBusyColor(); } else { busyColor = palette().color(QPalette::Window); if ((busyColor.blue() + busyColor.red() + busyColor.green()) > (256 / 2 * 3)) { // dark busyColor = busyColor.lighter(140); } else { // light busyColor = busyColor.darker(140); } } busyColor.setAlpha(EventViews::BUSY_BACKGROUND_ALPHA); dbp.fillRect(pt1.x(), pt1.y(), d->mGridSpacingX, cy + ch, busyColor); } } } // draw selection if (d->mHasSelection && d->mAgendaView->dateRangeSelectionEnabled()) { QPoint pt, pt1; QColor highlightColor; if (!d->preferences()->useSystemColor()) { highlightColor = d->preferences()->agendaGridHighlightColor(); } else { highlightColor = palette().color(QPalette::Highlight); } if (d->mSelectionEndCell.x() > d->mSelectionStartCell.x()) { // multi day selection // draw start day pt = gridToContents(d->mSelectionStartCell); pt1 = gridToContents(QPoint(d->mSelectionStartCell.x() + 1, d->mRows + 1)); dbp.fillRect(QRect(pt, pt1), highlightColor); // draw all other days between the start day and the day of the selection end for (int c = d->mSelectionStartCell.x() + 1; c < d->mSelectionEndCell.x(); ++c) { pt = gridToContents(QPoint(c, 0)); pt1 = gridToContents(QPoint(c + 1, d->mRows + 1)); dbp.fillRect(QRect(pt, pt1), highlightColor); } // draw end day pt = gridToContents(QPoint(d->mSelectionEndCell.x(), 0)); pt1 = gridToContents(d->mSelectionEndCell + QPoint(1, 1)); dbp.fillRect(QRect(pt, pt1), highlightColor); } else { // single day selection pt = gridToContents(d->mSelectionStartCell); pt1 = gridToContents(d->mSelectionEndCell + QPoint(1, 1)); dbp.fillRect(QRect(pt, pt1), highlightColor); } } // Compute the grid line color for both the hour and half-hour // The grid colors are always computed as a function of the palette's windowText color. QPen hourPen; QPen halfHourPen; const QColor windowTextColor = palette().color(QPalette::WindowText); if (windowTextColor.red() + windowTextColor.green() + windowTextColor.blue() < (256 / 2 * 3)) { // dark grey line hourPen = windowTextColor.lighter(200); halfHourPen = windowTextColor.lighter(500); } else { // light grey line hourPen = windowTextColor.darker(150); halfHourPen = windowTextColor.darker(200); } dbp.setPen(hourPen); // Draw vertical lines of grid, start with the last line not yet visible double x = (int(cx / d->mGridSpacingX)) * d->mGridSpacingX; while (x < cx + cw) { dbp.drawLine(int(x), cy, int(x), cy + ch); x += d->mGridSpacingX; } // Draw horizontal lines of grid double y = (int(cy / (2 * lGridSpacingY))) * 2 * lGridSpacingY; while (y < cy + ch) { dbp.drawLine(cx, int(y), cx + cw, int(y)); y += 2 * lGridSpacingY; } y = (2 * int(cy / (2 * lGridSpacingY)) + 1) * lGridSpacingY; dbp.setPen(halfHourPen); while (y < cy + ch) { dbp.drawLine(cx, int(y), cx + cw, int(y)); y += 2 * lGridSpacingY; } p->drawPixmap(cx, cy, db); } /* Convert srcollview contents coordinates to agenda grid coordinates. */ QPoint Agenda::contentsToGrid(const QPoint &pos) const { int gx = int(QApplication::isRightToLeft() ? d->mColumns - pos.x() / d->mGridSpacingX : pos.x() / d->mGridSpacingX); int gy = int(pos.y() / d->mGridSpacingY); return QPoint(gx, gy); } /* Convert agenda grid coordinates to scrollview contents coordinates. */ QPoint Agenda::gridToContents(const QPoint &gpos) const { int x = int(QApplication::isRightToLeft() ? (d->mColumns - gpos.x()) * d->mGridSpacingX : gpos.x() * d->mGridSpacingX); int y = int(gpos.y() * d->mGridSpacingY); return QPoint(x, y); } /* Return Y coordinate corresponding to time. Coordinates are rounded to fit into the grid. */ int Agenda::timeToY(const QTime &time) const { int minutesPerCell = 24 * 60 / d->mRows; int timeMinutes = time.hour() * 60 + time.minute(); int Y = (timeMinutes + (minutesPerCell / 2)) / minutesPerCell; return Y; } /* Return time corresponding to cell y coordinate. Coordinates are rounded to fit into the grid. */ QTime Agenda::gyToTime(int gy) const { int secondsPerCell = 24 * 60 * 60 / d->mRows; int timeSeconds = secondsPerCell * gy; QTime time(0, 0, 0); if (timeSeconds < 24 * 60 * 60) { time = time.addSecs(timeSeconds); } else { time.setHMS(23, 59, 59); } return time; } QVector<int> Agenda::minContentsY() const { QVector<int> minArray; minArray.fill(timeToY(QTime(23, 59)), d->mSelectedDates.count()); for (const AgendaItem::QPtr &item : qAsConst(d->mItems)) { if (item) { int ymin = item->cellYTop(); int index = item->cellXLeft(); if (index >= 0 && index < (int)(d->mSelectedDates.count())) { if (ymin < minArray[index] && !d->mItemsToDelete.contains(item)) { minArray[index] = ymin; } } } } return minArray; } QVector<int> Agenda::maxContentsY() const { QVector<int> maxArray; maxArray.fill(timeToY(QTime(0, 0)), d->mSelectedDates.count()); for (const AgendaItem::QPtr &item : qAsConst(d->mItems)) { if (item) { int ymax = item->cellYBottom(); int index = item->cellXLeft(); if (index >= 0 && index < (int)(d->mSelectedDates.count())) { if (ymax > maxArray[index] && !d->mItemsToDelete.contains(item)) { maxArray[index] = ymax; } } } } return maxArray; } void Agenda::setStartTime(QTime startHour) { const double startPos = (startHour.hour() / 24. + startHour.minute() / 1440. + startHour.second() / 86400.) *d->mRows * gridSpacingY(); verticalScrollBar()->setValue(startPos); } /* Insert AgendaItem into agenda. */ AgendaItem::QPtr Agenda::insertItem(const KCalendarCore::Incidence::Ptr &incidence, const QDateTime &recurrenceId, int X, int YTop, int YBottom, int itemPos, int itemCount, bool isSelected) { if (d->mAllDayMode) { qCDebug(CALENDARVIEW_LOG) << "using this in all-day mode is illegal."; return nullptr; } d->mActionType = NOP; AgendaItem::QPtr agendaItem = createAgendaItem(incidence, itemPos, itemCount, recurrenceId, isSelected); if (!agendaItem) { return AgendaItem::QPtr(); } if (YBottom <= YTop) { qCDebug(CALENDARVIEW_LOG) << "Text:" << agendaItem->text() << " YSize<0"; YBottom = YTop; } agendaItem->resize(int((X + 1) * d->mGridSpacingX) - int(X * d->mGridSpacingX), int(YTop * d->mGridSpacingY) - int((YBottom + 1) * d->mGridSpacingY)); agendaItem->setCellXY(X, YTop, YBottom); agendaItem->setCellXRight(X); agendaItem->setResourceColor(d->mCalendar->resourceColor(incidence)); agendaItem->installEventFilter(this); agendaItem->move(int(X * d->mGridSpacingX), int(YTop * d->mGridSpacingY)); d->mItems.append(agendaItem); placeSubCells(agendaItem); agendaItem->show(); marcus_bains(); return agendaItem; } /* Insert all-day AgendaItem into agenda. */ AgendaItem::QPtr Agenda::insertAllDayItem(const KCalendarCore::Incidence::Ptr &incidence, const QDateTime &recurrenceId, int XBegin, int XEnd, bool isSelected) { if (!d->mAllDayMode) { qCCritical(CALENDARVIEW_LOG) << "using this in non all-day mode is illegal."; return nullptr; } d->mActionType = NOP; AgendaItem::QPtr agendaItem = createAgendaItem(incidence, 1, 1, recurrenceId, isSelected); if (!agendaItem) { return AgendaItem::QPtr(); } agendaItem->setCellXY(XBegin, 0, 0); agendaItem->setCellXRight(XEnd); const double startIt = d->mGridSpacingX * (agendaItem->cellXLeft()); const double endIt = d->mGridSpacingX * (agendaItem->cellWidth() + agendaItem->cellXLeft()); agendaItem->resize(int(endIt) - int(startIt), int(d->mGridSpacingY)); agendaItem->installEventFilter(this); agendaItem->setResourceColor(d->mCalendar->resourceColor(incidence)); agendaItem->move(int(XBegin * d->mGridSpacingX), 0); d->mItems.append(agendaItem); placeSubCells(agendaItem); agendaItem->show(); return agendaItem; } AgendaItem::QPtr Agenda::createAgendaItem(const KCalendarCore::Incidence::Ptr &incidence, int itemPos, int itemCount, const QDateTime &recurrenceId, bool isSelected) { if (!incidence) { qCWarning(CALENDARVIEW_LOG) << "Agenda::createAgendaItem() item is invalid."; return AgendaItem::QPtr(); } AgendaItem::QPtr agendaItem = new AgendaItem(d->mAgendaView, d->mCalendar, incidence, itemPos, itemCount, recurrenceId, isSelected, this); connect(agendaItem.data(), &AgendaItem::removeAgendaItem, this, &Agenda::removeAgendaItem); connect(agendaItem.data(), &AgendaItem::showAgendaItem, this, &Agenda::showAgendaItem); d->mAgendaItemsById.insert(incidence->uid(), agendaItem); return agendaItem; } void Agenda::insertMultiItem(const KCalendarCore::Incidence::Ptr &event, const QDateTime &recurrenceId, int XBegin, int XEnd, int YTop, int YBottom, bool isSelected) { KCalendarCore::Event::Ptr ev = CalendarSupport::event(event); Q_ASSERT(ev); if (d->mAllDayMode) { qCDebug(CALENDARVIEW_LOG) << "using this in all-day mode is illegal."; return; } d->mActionType = NOP; int cellX, cellYTop, cellYBottom; QString newtext; int width = XEnd - XBegin + 1; int count = 0; AgendaItem::QPtr current = nullptr; QList<AgendaItem::QPtr> multiItems; int visibleCount = d->mSelectedDates.first().daysTo(d->mSelectedDates.last()); for (cellX = XBegin; cellX <= XEnd; ++cellX) { ++count; //Only add the items that are visible. if (cellX >= 0 && cellX <= visibleCount) { if (cellX == XBegin) { cellYTop = YTop; } else { cellYTop = 0; } if (cellX == XEnd) { cellYBottom = YBottom; } else { cellYBottom = rows() - 1; } newtext = QStringLiteral("(%1/%2): ").arg(count).arg(width); newtext.append(ev->summary()); current = insertItem(event, recurrenceId, cellX, cellYTop, cellYBottom, count, width, isSelected); Q_ASSERT(current); current->setText(newtext); multiItems.append(current); } } QList<AgendaItem::QPtr>::iterator it = multiItems.begin(); QList<AgendaItem::QPtr>::iterator e = multiItems.end(); if (it != e) { // .first asserts if the list is empty AgendaItem::QPtr first = multiItems.first(); AgendaItem::QPtr last = multiItems.last(); AgendaItem::QPtr prev = nullptr, next = nullptr; while (it != e) { AgendaItem::QPtr item = *it; ++it; next = (it == e) ? nullptr : (*it); if (item) { item->setMultiItem((item == first) ? nullptr : first, prev, next, (item == last) ? nullptr : last); } prev = item; } } marcus_bains(); } void Agenda::removeIncidence(const KCalendarCore::Incidence::Ptr &incidence) { if (!incidence) { qCWarning(CALENDARVIEW_LOG) << "Agenda::removeIncidence() incidence is invalid" << incidence->uid(); return; } if (d->isQueuedForDeletion(incidence->uid())) { return; // It's already queued for deletion } const AgendaItem::List agendaItems = d->mAgendaItemsById.values(incidence->uid()); if (agendaItems.isEmpty()) { // We're not displaying such item // qCDebug(CALENDARVIEW_LOG) << "Ignoring"; return; } for (const AgendaItem::QPtr &agendaItem : agendaItems) { if (agendaItem) { if (incidence->instanceIdentifier() != agendaItem->incidence()->instanceIdentifier()) { continue; } if (!removeAgendaItem(agendaItem)) { qCWarning(CALENDARVIEW_LOG) << "Agenda::removeIncidence() Failed to remove " << incidence->uid(); } } } } void Agenda::showAgendaItem(const AgendaItem::QPtr &agendaItem) { if (!agendaItem) { qCCritical(CALENDARVIEW_LOG) << "Show what?"; return; } agendaItem->hide(); agendaItem->setParent(this); if (!d->mItems.contains(agendaItem)) { d->mItems.append(agendaItem); } placeSubCells(agendaItem); agendaItem->show(); } bool Agenda::removeAgendaItem(const AgendaItem::QPtr &agendaItem) { Q_ASSERT(agendaItem); // we found the item. Let's remove it and update the conflicts QList<AgendaItem::QPtr> conflictItems = agendaItem->conflictItems(); // removeChild(thisItem); bool taken = d->mItems.removeAll(agendaItem) > 0; d->mAgendaItemsById.remove(agendaItem->incidence()->uid(), agendaItem); QList<AgendaItem::QPtr>::iterator it; for (it = conflictItems.begin(); it != conflictItems.end(); ++it) { if (*it) { (*it)->setSubCells((*it)->subCells() - 1); } } for (it = conflictItems.begin(); it != conflictItems.end(); ++it) { // the item itself is also in its own conflictItems list! if (*it && *it != agendaItem) { placeSubCells(*it); } } d->mItemsToDelete.append(agendaItem); d->mItemsQueuedForDeletion.insert(agendaItem->incidence()->uid()); agendaItem->setVisible(false); QTimer::singleShot(0, this, &Agenda::deleteItemsToDelete); return taken; } void Agenda::deleteItemsToDelete() { qDeleteAll(d->mItemsToDelete); d->mItemsToDelete.clear(); d->mItemsQueuedForDeletion.clear(); } /*QSizePolicy Agenda::sizePolicy() const { // Thought this would make the all-day event agenda minimum size and the // normal agenda take the remaining space. But it doesn't work. The QSplitter // don't seem to think that an Expanding widget needs more space than a // Preferred one. // But it doesn't hurt, so it stays. if (mAllDayMode) { return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred); } else { return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); } }*/ /* Overridden from QScrollView to provide proper resizing of AgendaItems. */ void Agenda::resizeEvent(QResizeEvent *ev) { QSize newSize(ev->size()); if (d->mAllDayMode) { d->mGridSpacingX = static_cast<double>(newSize.width()) / d->mColumns; d->mGridSpacingY = newSize.height(); } else { d->mGridSpacingX = static_cast<double>(newSize.width()) / d->mColumns; // make sure that there are not more than 24 per day d->mGridSpacingY = static_cast<double>(newSize.height()) / d->mRows; if (d->mGridSpacingY < d->mDesiredGridSpacingY) { d->mGridSpacingY = d->mDesiredGridSpacingY; } } calculateWorkingHours(); QTimer::singleShot(0, this, &Agenda::resizeAllContents); Q_EMIT gridSpacingYChanged(d->mGridSpacingY * 4); QWidget::resizeEvent(ev); updateGeometry(); } void Agenda::resizeAllContents() { double subCellWidth; for (const AgendaItem::QPtr &item : qAsConst(d->mItems)) { if (item) { subCellWidth = calcSubCellWidth(item); placeAgendaItem(item, subCellWidth); } } /* if (d->mAllDayMode) { foreach (const AgendaItem::QPtr &item, d->mItems) { if (item) { subCellWidth = calcSubCellWidth(item); placeAgendaItem(item, subCellWidth); } } } else { foreach (const AgendaItem::QPtr &item, d->mItems) { if (item) { subCellWidth = calcSubCellWidth(item); placeAgendaItem(item, subCellWidth); } } } */ checkScrollBoundaries(); marcus_bains(); update(); } void Agenda::scrollUp() { int currentValue = verticalScrollBar()->value(); verticalScrollBar()->setValue(currentValue - d->mScrollOffset); } void Agenda::scrollDown() { int currentValue = verticalScrollBar()->value(); verticalScrollBar()->setValue(currentValue + d->mScrollOffset); } QSize Agenda::minimumSize() const { return sizeHint(); } QSize Agenda::minimumSizeHint() const { return sizeHint(); } int Agenda::minimumHeight() const { // all day agenda never has scrollbars and the scrollarea will // resize it to fit exactly on the viewport. if (d->mAllDayMode) { return 0; } else { return d->mGridSpacingY * d->mRows; } } void Agenda::updateConfig() { const double oldGridSpacingY = d->mGridSpacingY; if (!d->mAllDayMode) { d->mDesiredGridSpacingY = d->preferences()->hourSize(); if (d->mDesiredGridSpacingY < 4 || d->mDesiredGridSpacingY > 30) { d->mDesiredGridSpacingY = 10; } /* // make sure that there are not more than 24 per day d->mGridSpacingY = static_cast<double>(height()) / d->mRows; if (d->mGridSpacingY < d->mDesiredGridSpacingY || true) { d->mGridSpacingY = d->mDesiredGridSpacingY; } */ //can be two doubles equal?, it's better to compare them with an epsilon if (fabs(oldGridSpacingY - d->mDesiredGridSpacingY) > 0.1) { d->mGridSpacingY = d->mDesiredGridSpacingY; updateGeometry(); } } calculateWorkingHours(); marcus_bains(); } void Agenda::checkScrollBoundaries() { // Invalidate old values to force update d->mOldLowerScrollValue = -1; d->mOldUpperScrollValue = -1; checkScrollBoundaries(verticalScrollBar()->value()); } void Agenda::checkScrollBoundaries(int v) { int yMin = int((v) / d->mGridSpacingY); int yMax = int((v + d->mScrollArea->height()) / d->mGridSpacingY); if (yMin != d->mOldLowerScrollValue) { d->mOldLowerScrollValue = yMin; Q_EMIT lowerYChanged(yMin); } if (yMax != d->mOldUpperScrollValue) { d->mOldUpperScrollValue = yMax; Q_EMIT upperYChanged(yMax); } } int Agenda::visibleContentsYMin() const { int v = verticalScrollBar()->value(); return int(v / d->mGridSpacingY); } int Agenda::visibleContentsYMax() const { int v = verticalScrollBar()->value(); return int((v + d->mScrollArea->height()) / d->mGridSpacingY); } void Agenda::deselectItem() { if (d->mSelectedItem.isNull()) { return; } const KCalendarCore::Incidence::Ptr selectedItem = d->mSelectedItem->incidence(); for (AgendaItem::QPtr item : qAsConst(d->mItems)) { if (item) { const KCalendarCore::Incidence::Ptr itemInc = item->incidence(); if (itemInc && selectedItem && itemInc->uid() == selectedItem->uid()) { item->select(false); } } } d->mSelectedItem = nullptr; } void Agenda::selectItem(const AgendaItem::QPtr &item) { if ((AgendaItem::QPtr)d->mSelectedItem == item) { return; } deselectItem(); if (item == nullptr) { Q_EMIT incidenceSelected(KCalendarCore::Incidence::Ptr(), QDate()); return; } d->mSelectedItem = item; d->mSelectedItem->select(); Q_ASSERT(d->mSelectedItem->incidence()); d->mSelectedId = d->mSelectedItem->incidence()->uid(); for (AgendaItem::QPtr item : qAsConst(d->mItems)) { if (item && item->incidence()->uid() == d->mSelectedId) { item->select(); } } Q_EMIT incidenceSelected(d->mSelectedItem->incidence(), d->mSelectedItem->occurrenceDate()); } void Agenda::selectIncidenceByUid(const QString &uid) { for (const AgendaItem::QPtr &item : qAsConst(d->mItems)) { if (item && item->incidence()->uid() == uid) { selectItem(item); break; } } } void Agenda::selectItem(const Akonadi::Item &item) { selectIncidenceByUid(CalendarSupport::incidence(item)->uid()); } // This function seems never be called. void Agenda::keyPressEvent(QKeyEvent *kev) { switch (kev->key()) { case Qt::Key_PageDown: verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd); break; case Qt::Key_PageUp: verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub); break; case Qt::Key_Down: verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepAdd); break; case Qt::Key_Up: verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepSub); break; default: ; } } void Agenda::calculateWorkingHours() { d->mWorkingHoursEnable = !d->mAllDayMode; QTime tmp = d->preferences()->workingHoursStart().time(); d->mWorkingHoursYTop = int(4 * d->mGridSpacingY * (tmp.hour() + tmp.minute() / 60. + tmp.second() / 3600.)); tmp = d->preferences()->workingHoursEnd().time(); d->mWorkingHoursYBottom = int(4 * d->mGridSpacingY * (tmp.hour() + tmp.minute() / 60. + tmp.second() / 3600.) - 1); } void Agenda::setDateList(const KCalendarCore::DateList &selectedDates) { d->mSelectedDates = selectedDates; marcus_bains(); } KCalendarCore::DateList Agenda::dateList() const { return d->mSelectedDates; } void Agenda::setCalendar(const MultiViewCalendar::Ptr &cal) { d->mCalendar = cal; } void Agenda::setIncidenceChanger(Akonadi::IncidenceChanger *changer) { d->mChanger = changer; } void Agenda::setHolidayMask(QVector<bool> *mask) { d->mHolidayMask = mask; } void Agenda::contentsMousePressEvent(QMouseEvent *event) { Q_UNUSED(event); } QSize Agenda::sizeHint() const { if (d->mAllDayMode) { return QWidget::sizeHint(); } else { return QSize(parentWidget()->width(), d->mGridSpacingY * d->mRows); } } QScrollBar *Agenda::verticalScrollBar() const { return d->mScrollArea->verticalScrollBar(); } QScrollArea *Agenda::scrollArea() const { return d->mScrollArea; } AgendaItem::List Agenda::agendaItems(const QString &uid) const { return d->mAgendaItemsById.values(uid); } AgendaScrollArea::AgendaScrollArea(bool isAllDay, AgendaView *agendaView, bool isInteractive, QWidget *parent) : QScrollArea(parent) { if (isAllDay) { mAgenda = new Agenda(agendaView, this, 1, isInteractive); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } else { mAgenda = new Agenda(agendaView, this, 1, 96, agendaView->preferences()->hourSize(), isInteractive); } setWidgetResizable(true); setWidget(mAgenda); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); mAgenda->setStartTime(agendaView->preferences()->dayBegins().time()); } AgendaScrollArea::~AgendaScrollArea() { } Agenda *AgendaScrollArea::agenda() const { return mAgenda; } 0707010000000C000081A40000000200000002000000015F0BF3C900002EB4000000000000000000000000000000000000004200000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/agenda.h/* Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_AGENDA_H #define EVENTVIEWS_AGENDA_H #include "eventviews_export.h" #include "agendaitem.h" #include "viewcalendar.h" #include <Item> #include <KCalendarCore/Todo> #include <QFrame> #include <QScrollArea> namespace Akonadi { class IncidenceChanger; } namespace EventViews { class Agenda; class AgendaItem; class AgendaView; class MarcusBains : public QFrame { Q_OBJECT public: explicit MarcusBains(EventView *eventView, Agenda *agenda = nullptr); void updateLocationRecalc(bool recalculate = false); ~MarcusBains() override; public Q_SLOTS: void updateLocation(); private: class Private; Private *const d; }; class EVENTVIEWS_EXPORT Agenda : public QWidget { Q_OBJECT public: Agenda(AgendaView *agendaView, QScrollArea *scrollArea, int columns, int rows, int rowSize, bool isInteractive); Agenda(AgendaView *agendaView, QScrollArea *scrollArea, int columns, bool isInteractive); ~Agenda() override; Q_REQUIRED_RESULT KCalendarCore::Incidence::Ptr selectedIncidence() const; Q_REQUIRED_RESULT QDate selectedIncidenceDate() const; QSize sizeHint() const override; QSize minimumSizeHint() const override; QSize minimumSize() const; int minimumHeight() const; // QSizePolicy sizePolicy() const; Q_REQUIRED_RESULT int contentsY() const { return -y(); } Q_REQUIRED_RESULT int contentsX() const { return x(); } QScrollBar *verticalScrollBar() const; QScrollArea *scrollArea() const; Q_REQUIRED_RESULT AgendaItem::List agendaItems(const QString &uid) const; /** Returns the uid of the last incidence that was selected. This persists across reloads and clear, so that if the same uid reappears, it can be reselected. */ Q_REQUIRED_RESULT QString lastSelectedItemUid() const; bool eventFilter(QObject *, QEvent *) override; void paintEvent(QPaintEvent *) override; Q_REQUIRED_RESULT QPoint contentsToGrid(const QPoint &pos) const; Q_REQUIRED_RESULT QPoint gridToContents(const QPoint &gpos) const; Q_REQUIRED_RESULT int timeToY(const QTime &time) const; Q_REQUIRED_RESULT QTime gyToTime(int y) const; Q_REQUIRED_RESULT QVector<int> minContentsY() const; Q_REQUIRED_RESULT QVector<int> maxContentsY() const; Q_REQUIRED_RESULT int visibleContentsYMin() const; Q_REQUIRED_RESULT int visibleContentsYMax() const; void setStartTime(QTime startHour); AgendaItem::QPtr insertItem(const KCalendarCore::Incidence::Ptr &incidence, const QDateTime &recurrenceId, int X, int YTop, int YBottom, int itemPos, int itemCount, bool isSelected); AgendaItem::QPtr insertAllDayItem(const KCalendarCore::Incidence::Ptr &event, const QDateTime &recurrenceId, int XBegin, int XEnd, bool isSelected); void insertMultiItem(const KCalendarCore::Incidence::Ptr &event, const QDateTime &recurrenceId, int XBegin, int XEnd, int YTop, int YBottom, bool isSelected); /** Removes an event and all its multi-items from the agenda. This function removes the items from the view, but doesn't delete them immediately. Instead, they are queued in mItemsToDelete and later deleted by the slot deleteItemsToDelete() (called by QTimer::singleShot ). @param incidence The pointer to the incidence that should be removed. */ void removeIncidence(const KCalendarCore::Incidence::Ptr &incidence); void changeColumns(int columns); Q_REQUIRED_RESULT int columns() const; Q_REQUIRED_RESULT int rows() const; Q_REQUIRED_RESULT double gridSpacingX() const; Q_REQUIRED_RESULT double gridSpacingY() const; void clear(); /** Update configuration from preference settings */ void updateConfig(); void checkScrollBoundaries(); void setHolidayMask(QVector<bool> *); void setDateList(const KCalendarCore::DateList &selectedDates); Q_REQUIRED_RESULT KCalendarCore::DateList dateList() const; void setCalendar(const EventViews::MultiViewCalendar::Ptr &cal); void setIncidenceChanger(Akonadi::IncidenceChanger *changer); public Q_SLOTS: void scrollUp(); void scrollDown(); void checkScrollBoundaries(int); /** Deselect selected items. This function does not Q_EMIT any signals. */ void deselectItem(); void clearSelection(); /** Select item. If the argument is 0, the currently selected item gets deselected. This function emits the itemSelected(bool) signal to inform about selection/deselection of events. */ void selectItem(const AgendaItem::QPtr &); /** Selects the item associated with a given Akonadi Item id. Linear search, use carefully. @param id the item id of the item that should be selected. If no such item exists, the selection is not changed. */ void selectIncidenceByUid(const QString &id); void selectItem(const Akonadi::Item &item); bool removeAgendaItem(const AgendaItem::QPtr &item); void showAgendaItem(const AgendaItem::QPtr &item); Q_SIGNALS: void newEventSignal(); void newTimeSpanSignal(const QPoint &, const QPoint &); void newStartSelectSignal(); void showIncidenceSignal(const KCalendarCore::Incidence::Ptr &); void editIncidenceSignal(const KCalendarCore::Incidence::Ptr &); void deleteIncidenceSignal(const KCalendarCore::Incidence::Ptr &); void showIncidencePopupSignal(const KCalendarCore::Incidence::Ptr &, const QDate &); void showNewEventPopupSignal(); void incidenceSelected(const KCalendarCore::Incidence::Ptr &, const QDate &); void lowerYChanged(int); void upperYChanged(int); void startDragSignal(const KCalendarCore::Incidence::Ptr &); void droppedIncidences(const KCalendarCore::Incidence::List &, const QPoint &gpos, bool allDay); void droppedIncidences(const QList<QUrl> &, const QPoint &gpos, bool allDay); void enableAgendaUpdate(bool enable); void zoomView(const int delta, const QPoint &pos, const Qt::Orientation); void mousePosSignal(const QPoint &pos); void enterAgenda(); void leaveAgenda(); void gridSpacingYChanged(double); private: enum MouseActionType { NOP, MOVE, SELECT, RESIZETOP, RESIZEBOTTOM, RESIZELEFT, RESIZERIGHT }; AgendaItem::QPtr createAgendaItem(const KCalendarCore::Incidence::Ptr &incidence, int itemPos, int itemCount, const QDateTime &recurrentId, bool isSelected); protected: /** Draw the background grid of the agenda. @p cw grid width @p ch grid height */ void drawContents(QPainter *p, int cx, int cy, int cw, int ch); int columnWidth(int column) const; void resizeEvent(QResizeEvent *) override; /** Handles mouse events. Called from eventFilter */ virtual bool eventFilter_mouse(QObject *, QMouseEvent *); #ifndef QT_NO_WHEELEVENT /** Handles mousewheel events. Called from eventFilter */ virtual bool eventFilter_wheel(QObject *, QWheelEvent *); #endif /** Handles key events. Called from eventFilter */ virtual bool eventFilter_key(QObject *, QKeyEvent *); /** Handles drag and drop events. Called from eventFilter */ virtual bool eventFilter_drag(QObject *, QDropEvent *); /** returns RESIZELEFT if pos is near the lower edge of the action item, RESIZERIGHT if pos is near the higher edge, and MOVE otherwise. If --reverse is used, RESIZELEFT still means resizing the beginning of the event, although that means moving to the right! horizontal is the same as mAllDayAgenda. @param horizontal Whether horizontal resizing is possible @param pos The current mouse position @param item The affected item */ MouseActionType isInResizeArea(bool horizontal, const QPoint &pos, const AgendaItem::QPtr &item); /** Return whether the cell specified by the grid point belongs to the current select */ bool ptInSelection(const QPoint &gpos) const; /** Start selecting time span. */ void startSelectAction(const QPoint &viewportPos); /** Select time span. */ void performSelectAction(const QPoint &viewportPos); /** Emd selecting time span. */ void endSelectAction(const QPoint &viewportPos); /** Start moving/resizing agenda item */ void startItemAction(const QPoint &viewportPos); /** Move/resize agenda item */ void performItemAction(const QPoint &viewportPos); /** End moving/resizing agenda item */ void endItemAction(); /** Set cursor, when no item action is in progress */ void setNoActionCursor(const AgendaItem::QPtr &moveItem, const QPoint &viewportPos); /** Sets the cursor according to the given action type. @param actionType The type of action for which the cursor should be set. @param acting If true, the corresponding action is running (e.g. the item is currently being moved by the user). If false the cursor should just indicate that the corresponding action is possible */ void setActionCursor(int actionType, bool acting = false); /** calculate the width of the column subcells of the given item */ double calcSubCellWidth(const AgendaItem::QPtr &item); /** Move and resize the given item to the correct position */ void placeAgendaItem(const AgendaItem::QPtr &item, double subCellWidth); /** Place agenda item in agenda and adjust other cells if necessary */ void placeSubCells(const AgendaItem::QPtr &placeItem); /** Place the agenda item at the correct position (ignoring conflicting items) */ void adjustItemPosition(const AgendaItem::QPtr &item); /** Process the keyevent, including the ignored keyevents of eventwidgets. * Implements pgup/pgdn and cursor key navigation in the view. */ void keyPressEvent(QKeyEvent *) override; void calculateWorkingHours(); virtual void contentsMousePressEvent(QMouseEvent *); protected Q_SLOTS: /** delete the items that are queued for deletion */ void deleteItemsToDelete(); /** Resizes all the child elements after the size of the agenda changed. This is needed because Qt seems to have a bug when the resizeEvent of one of the widgets in a splitter takes a lot of time / does a lot of resizes.... see bug 80114 */ void resizeAllContents(); private: void init(); void marcus_bains(); private: class Private; Private *const d; }; class AgendaScrollArea : public QScrollArea { Q_OBJECT public: AgendaScrollArea(bool allDay, AgendaView *agendaView, bool isInteractive, QWidget *parent); ~AgendaScrollArea(); Agenda *agenda() const; private: Agenda *mAgenda = nullptr; }; } #endif 0707010000000D000081A40000000200000002000000015F0BF3C900009D4C000000000000000000000000000000000000004800000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/agendaitem.cpp/* Copyright (c) 2000,2001,2003 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "agendaitem.h" #include "eventview.h" #include "viewcalendar.h" #include "helper.h" #include "prefs.h" #include "prefs_base.h" // for enums #include <CalendarSupport/KCalPrefs> #include <CalendarSupport/Utils> #include <KContacts/VCardDrag> #include <KCalUtils/ICalDrag> #include <KCalUtils/VCalDrag> #include <KCalUtils/IncidenceFormatter> #include <KEmailAddress> #include <KLocalizedString> #include <KMessageBox> #include <KWordWrap> #include <QDragEnterEvent> #include <QPainter> #include <QPainterPath> #include <QPixmapCache> #include <QToolTip> #include <QMimeData> #include <QLocale> using namespace KCalendarCore; using namespace EventViews; //----------------------------------------------------------------------------- QPixmap *AgendaItem::alarmPxmp = nullptr; QPixmap *AgendaItem::recurPxmp = nullptr; QPixmap *AgendaItem::readonlyPxmp = nullptr; QPixmap *AgendaItem::replyPxmp = nullptr; QPixmap *AgendaItem::groupPxmp = nullptr; QPixmap *AgendaItem::groupPxmpTent = nullptr; QPixmap *AgendaItem::organizerPxmp = nullptr; QPixmap *AgendaItem::eventPxmp = nullptr; //----------------------------------------------------------------------------- AgendaItem::AgendaItem(EventView *eventView, const MultiViewCalendar::Ptr &calendar, const KCalendarCore::Incidence::Ptr &item, int itemPos, int itemCount, const QDateTime &qd, bool isSelected, QWidget *parent) : QWidget(parent) , mEventView(eventView) , mCalendar(calendar) , mIncidence(item) , mOccurrenceDateTime(qd) , mValid(true) , mCloned(false) , mSelected(isSelected) , mSpecialEvent(false) { if (!mIncidence) { mValid = false; return; } mIncidence = Incidence::Ptr(mIncidence->clone()); if (mIncidence->customProperty("KABC", "BIRTHDAY") == QLatin1String("YES") || mIncidence->customProperty("KABC", "ANNIVERSARY") == QLatin1String("YES")) { const int years = EventViews::yearDiff(mIncidence->dtStart().date(), qd.toLocalTime().date()); if (years > 0) { mIncidence->setReadOnly(false); mIncidence->setSummary(i18np("%2 (1 year)", "%2 (%1 years)", years, mIncidence->summary())); mIncidence->setReadOnly(true); mCloned = true; } } mLabelText = mIncidence->summary(); mIconAlarm = false; mIconRecur = false; mIconReadonly = false; mIconReply = false; mIconGroup = false; mIconGroupTent = false; mIconOrganizer = false; mMultiItemInfo = nullptr; mStartMoveInfo = nullptr; mItemPos = itemPos; mItemCount = itemCount; QPalette pal = palette(); pal.setColor(QPalette::Window, Qt::transparent); setPalette(pal); setCellXY(0, 0, 1); setCellXRight(0); setMouseTracking(true); mResourceColor = QColor(); updateIcons(); setAcceptDrops(true); } AgendaItem::~AgendaItem() { } void AgendaItem::updateIcons() { if (!mValid) { return; } mIconReadonly = mIncidence->isReadOnly(); mIconRecur = mIncidence->recurs() || mIncidence->hasRecurrenceId(); mIconAlarm = mIncidence->hasEnabledAlarms(); if (mIncidence->attendeeCount() > 1) { if (mEventView->kcalPreferences()->thatIsMe(mIncidence->organizer().email())) { mIconReply = false; mIconGroup = false; mIconGroupTent = false; mIconOrganizer = true; } else { KCalendarCore::Attendee me = mIncidence->attendeeByMails(mEventView->kcalPreferences()->allEmails()); if (!me.isNull()) { if (me.status() == KCalendarCore::Attendee::NeedsAction && me.RSVP()) { mIconReply = true; mIconGroup = false; mIconGroupTent = false; mIconOrganizer = false; } else if (me.status() == KCalendarCore::Attendee::Tentative) { mIconReply = false; mIconGroup = false; mIconGroupTent = true; mIconOrganizer = false; } else { mIconReply = false; mIconGroup = true; mIconGroupTent = false; mIconOrganizer = false; } } else { mIconReply = false; mIconGroup = true; mIconGroupTent = false; mIconOrganizer = false; } } } update(); } void AgendaItem::select(bool selected) { if (mSelected != selected) { mSelected = selected; update(); } } bool AgendaItem::dissociateFromMultiItem() { if (!isMultiItem()) { return false; } AgendaItem::QPtr firstItem = firstMultiItem(); if (firstItem == this) { firstItem = nextMultiItem(); } AgendaItem::QPtr lastItem = lastMultiItem(); if (lastItem == this) { lastItem = prevMultiItem(); } AgendaItem::QPtr prevItem = prevMultiItem(); AgendaItem::QPtr nextItem = nextMultiItem(); if (prevItem) { prevItem->setMultiItem(firstItem, prevItem->prevMultiItem(), nextItem, lastItem); } if (nextItem) { nextItem->setMultiItem(firstItem, prevItem, nextItem->prevMultiItem(), lastItem); } delete mMultiItemInfo; mMultiItemInfo = nullptr; return true; } void AgendaItem::setIncidence(const KCalendarCore::Incidence::Ptr &incidence) { mValid = false; if (incidence) { mValid = true; mIncidence = incidence; mLabelText = mIncidence->summary(); updateIcons(); } } /* Return height of item in units of agenda cells */ int AgendaItem::cellHeight() const { return mCellYBottom - mCellYTop + 1; } /* Return height of item in units of agenda cells */ int AgendaItem::cellWidth() const { return mCellXRight - mCellXLeft + 1; } void AgendaItem::setOccurrenceDateTime(const QDateTime &qd) { mOccurrenceDateTime = qd; } QDate AgendaItem::occurrenceDate() const { return mOccurrenceDateTime.toLocalTime().date(); } void AgendaItem::setCellXY(int X, int YTop, int YBottom) { mCellXLeft = X; mCellYTop = YTop; mCellYBottom = YBottom; } void AgendaItem::setCellXRight(int XRight) { mCellXRight = XRight; } void AgendaItem::setCellX(int XLeft, int XRight) { mCellXLeft = XLeft; mCellXRight = XRight; } void AgendaItem::setCellY(int YTop, int YBottom) { mCellYTop = YTop; mCellYBottom = YBottom; } void AgendaItem::setMultiItem(const AgendaItem::QPtr &first, const AgendaItem::QPtr &prev, const AgendaItem::QPtr &next, const AgendaItem::QPtr &last) { if (!mMultiItemInfo) { mMultiItemInfo = new MultiItemInfo; } mMultiItemInfo->mFirstMultiItem = first; mMultiItemInfo->mPrevMultiItem = prev; mMultiItemInfo->mNextMultiItem = next; mMultiItemInfo->mLastMultiItem = last; } bool AgendaItem::isMultiItem() const { return mMultiItemInfo; } AgendaItem::QPtr AgendaItem::prependMoveItem(const AgendaItem::QPtr &e) { if (!e) { return nullptr; } AgendaItem::QPtr first = nullptr, last = nullptr; if (isMultiItem()) { first = mMultiItemInfo->mFirstMultiItem; last = mMultiItemInfo->mLastMultiItem; } if (!first) { first = this; } if (!last) { last = this; } e->setMultiItem(nullptr, nullptr, first, last); first->setMultiItem(e, e, first->nextMultiItem(), first->lastMultiItem()); AgendaItem::QPtr tmp = first->nextMultiItem(); while (tmp) { tmp->setMultiItem(e, tmp->prevMultiItem(), tmp->nextMultiItem(), tmp->lastMultiItem()); tmp = tmp->nextMultiItem(); } if (mStartMoveInfo && !e->moveInfo()) { e->mStartMoveInfo = new MultiItemInfo(*mStartMoveInfo); // e->moveInfo()->mFirstMultiItem = moveInfo()->mFirstMultiItem; // e->moveInfo()->mLastMultiItem = moveInfo()->mLastMultiItem; e->moveInfo()->mPrevMultiItem = nullptr; e->moveInfo()->mNextMultiItem = first; } if (first && first->moveInfo()) { first->moveInfo()->mPrevMultiItem = e; } return e; } AgendaItem::QPtr AgendaItem::appendMoveItem(const AgendaItem::QPtr &e) { if (!e) { return nullptr; } AgendaItem::QPtr first = nullptr, last = nullptr; if (isMultiItem()) { first = mMultiItemInfo->mFirstMultiItem; last = mMultiItemInfo->mLastMultiItem; } if (!first) { first = this; } if (!last) { last = this; } e->setMultiItem(first, last, nullptr, nullptr); AgendaItem::QPtr tmp = first; while (tmp) { tmp->setMultiItem(tmp->firstMultiItem(), tmp->prevMultiItem(), tmp->nextMultiItem(), e); tmp = tmp->nextMultiItem(); } last->setMultiItem(last->firstMultiItem(), last->prevMultiItem(), e, e); if (mStartMoveInfo && !e->moveInfo()) { e->mStartMoveInfo = new MultiItemInfo(*mStartMoveInfo); // e->moveInfo()->mFirstMultiItem = moveInfo()->mFirstMultiItem; // e->moveInfo()->mLastMultiItem = moveInfo()->mLastMultiItem; e->moveInfo()->mPrevMultiItem = last; e->moveInfo()->mNextMultiItem = nullptr; } if (last && last->moveInfo()) { last->moveInfo()->mNextMultiItem = e; } return e; } AgendaItem::QPtr AgendaItem::removeMoveItem(const AgendaItem::QPtr &e) { if (isMultiItem()) { AgendaItem::QPtr first = mMultiItemInfo->mFirstMultiItem; AgendaItem::QPtr next, prev; AgendaItem::QPtr last = mMultiItemInfo->mLastMultiItem; if (!first) { first = this; } if (!last) { last = this; } if (first == e) { first = first->nextMultiItem(); first->setMultiItem(nullptr, nullptr, first->nextMultiItem(), first->lastMultiItem()); } if (last == e) { last = last->prevMultiItem(); last->setMultiItem(last->firstMultiItem(), last->prevMultiItem(), nullptr, nullptr); } AgendaItem::QPtr tmp = first; if (first == last) { delete mMultiItemInfo; tmp = nullptr; mMultiItemInfo = nullptr; } while (tmp) { next = tmp->nextMultiItem(); prev = tmp->prevMultiItem(); if (e == next) { next = next->nextMultiItem(); } if (e == prev) { prev = prev->prevMultiItem(); } tmp->setMultiItem((tmp == first) ? nullptr : first, (tmp == prev) ? nullptr : prev, (tmp == next) ? nullptr : next, (tmp == last) ? nullptr : last); tmp = tmp->nextMultiItem(); } } return e; } void AgendaItem::startMove() { AgendaItem::QPtr first = this; if (isMultiItem() && mMultiItemInfo->mFirstMultiItem) { first = mMultiItemInfo->mFirstMultiItem; } first->startMovePrivate(); } void AgendaItem::startMovePrivate() { mStartMoveInfo = new MultiItemInfo; mStartMoveInfo->mStartCellXLeft = mCellXLeft; mStartMoveInfo->mStartCellXRight = mCellXRight; mStartMoveInfo->mStartCellYTop = mCellYTop; mStartMoveInfo->mStartCellYBottom = mCellYBottom; if (mMultiItemInfo) { mStartMoveInfo->mFirstMultiItem = mMultiItemInfo->mFirstMultiItem; mStartMoveInfo->mLastMultiItem = mMultiItemInfo->mLastMultiItem; mStartMoveInfo->mPrevMultiItem = mMultiItemInfo->mPrevMultiItem; mStartMoveInfo->mNextMultiItem = mMultiItemInfo->mNextMultiItem; } else { mStartMoveInfo->mFirstMultiItem = nullptr; mStartMoveInfo->mLastMultiItem = nullptr; mStartMoveInfo->mPrevMultiItem = nullptr; mStartMoveInfo->mNextMultiItem = nullptr; } if (isMultiItem() && mMultiItemInfo->mNextMultiItem) { mMultiItemInfo->mNextMultiItem->startMovePrivate(); } } void AgendaItem::resetMove() { if (mStartMoveInfo) { if (mStartMoveInfo->mFirstMultiItem) { mStartMoveInfo->mFirstMultiItem->resetMovePrivate(); } else { resetMovePrivate(); } } } void AgendaItem::resetMovePrivate() { if (mStartMoveInfo) { mCellXLeft = mStartMoveInfo->mStartCellXLeft; mCellXRight = mStartMoveInfo->mStartCellXRight; mCellYTop = mStartMoveInfo->mStartCellYTop; mCellYBottom = mStartMoveInfo->mStartCellYBottom; // if we don't have mMultiItemInfo, the item didn't span two days before, // and wasn't moved over midnight, either, so we don't have to reset // anything. Otherwise, restore from mMoveItemInfo if (mMultiItemInfo) { // It was already a multi-day info mMultiItemInfo->mFirstMultiItem = mStartMoveInfo->mFirstMultiItem; mMultiItemInfo->mPrevMultiItem = mStartMoveInfo->mPrevMultiItem; mMultiItemInfo->mNextMultiItem = mStartMoveInfo->mNextMultiItem; mMultiItemInfo->mLastMultiItem = mStartMoveInfo->mLastMultiItem; if (!mStartMoveInfo->mFirstMultiItem) { // This was the first multi-item when the move started, delete all previous AgendaItem::QPtr toDel = mStartMoveInfo->mPrevMultiItem; AgendaItem::QPtr nowDel = nullptr; while (toDel) { nowDel = toDel; if (nowDel->moveInfo()) { toDel = nowDel->moveInfo()->mPrevMultiItem; } Q_EMIT removeAgendaItem(nowDel); } mMultiItemInfo->mFirstMultiItem = nullptr; mMultiItemInfo->mPrevMultiItem = nullptr; } if (!mStartMoveInfo->mLastMultiItem) { // This was the last multi-item when the move started, delete all next AgendaItem::QPtr toDel = mStartMoveInfo->mNextMultiItem; AgendaItem::QPtr nowDel = nullptr; while (toDel) { nowDel = toDel; if (nowDel->moveInfo()) { toDel = nowDel->moveInfo()->mNextMultiItem; } Q_EMIT removeAgendaItem(nowDel); } mMultiItemInfo->mLastMultiItem = nullptr; mMultiItemInfo->mNextMultiItem = nullptr; } if (mStartMoveInfo->mFirstMultiItem == nullptr && mStartMoveInfo->mLastMultiItem == nullptr) { // it was a single-day event before we started the move. delete mMultiItemInfo; mMultiItemInfo = nullptr; } } delete mStartMoveInfo; mStartMoveInfo = nullptr; } Q_EMIT showAgendaItem(this); if (nextMultiItem()) { nextMultiItem()->resetMovePrivate(); } } void AgendaItem::endMove() { AgendaItem::QPtr first = firstMultiItem(); if (!first) { first = this; } first->endMovePrivate(); } void AgendaItem::endMovePrivate() { if (mStartMoveInfo) { // if first, delete all previous if (!firstMultiItem() || firstMultiItem() == this) { AgendaItem::QPtr toDel = mStartMoveInfo->mPrevMultiItem; AgendaItem::QPtr nowDel = nullptr; while (toDel) { nowDel = toDel; if (nowDel->moveInfo()) { toDel = nowDel->moveInfo()->mPrevMultiItem; } Q_EMIT removeAgendaItem(nowDel); } } // if last, delete all next if (!lastMultiItem() || lastMultiItem() == this) { AgendaItem::QPtr toDel = mStartMoveInfo->mNextMultiItem; AgendaItem::QPtr nowDel = nullptr; while (toDel) { nowDel = toDel; if (nowDel->moveInfo()) { toDel = nowDel->moveInfo()->mNextMultiItem; } Q_EMIT removeAgendaItem(nowDel); } } // also delete the moving info delete mStartMoveInfo; mStartMoveInfo = nullptr; if (nextMultiItem()) { nextMultiItem()->endMovePrivate(); } } } void AgendaItem::moveRelative(int dx, int dy) { int newXLeft = cellXLeft() + dx; int newXRight = cellXRight() + dx; int newYTop = cellYTop() + dy; int newYBottom = cellYBottom() + dy; setCellXY(newXLeft, newYTop, newYBottom); setCellXRight(newXRight); } void AgendaItem::expandTop(int dy, const bool allowOverLimit) { int newYTop = cellYTop() + dy; int newYBottom = cellYBottom(); if (newYTop > newYBottom && !allowOverLimit) { newYTop = newYBottom; } setCellY(newYTop, newYBottom); } void AgendaItem::expandBottom(int dy) { int newYTop = cellYTop(); int newYBottom = cellYBottom() + dy; if (newYBottom < newYTop) { newYBottom = newYTop; } setCellY(newYTop, newYBottom); } void AgendaItem::expandLeft(int dx) { int newXLeft = cellXLeft() + dx; int newXRight = cellXRight(); if (newXLeft > newXRight) { newXLeft = newXRight; } setCellX(newXLeft, newXRight); } void AgendaItem::expandRight(int dx) { int newXLeft = cellXLeft(); int newXRight = cellXRight() + dx; if (newXRight < newXLeft) { newXRight = newXLeft; } setCellX(newXLeft, newXRight); } void AgendaItem::dragEnterEvent(QDragEnterEvent *e) { const QMimeData *md = e->mimeData(); if (KCalUtils::ICalDrag::canDecode(md) || KCalUtils::VCalDrag::canDecode(md)) { // TODO: Allow dragging events/todos onto other events to create a relation e->ignore(); return; } if (KContacts::VCardDrag::canDecode(md) || md->hasText()) { e->accept(); } else { e->ignore(); } } void AgendaItem::addAttendee(const QString &newAttendee) { if (!mValid) { return; } QString name, email; KEmailAddress::extractEmailAddressAndName(newAttendee, email, name); if (!(name.isEmpty() && email.isEmpty())) { mIncidence->addAttendee(KCalendarCore::Attendee(name, email)); KMessageBox::information( this, i18n("Attendee \"%1\" added to the calendar item \"%2\"", KEmailAddress::normalizedAddress(name, email, QString()), text()), i18n("Attendee added"), QStringLiteral("AttendeeDroppedAdded")); } } void AgendaItem::dropEvent(QDropEvent *e) { // TODO: Organize this better: First check for attachment // (not only file, also any other url!), then if it's a vcard, // otherwise check for attendees, then if the data is binary, // add a binary attachment. if (!mValid) { return; } const QMimeData *md = e->mimeData(); bool decoded = md->hasText(); QString text = md->text(); if (decoded && text.startsWith(QLatin1String("file:"))) { mIncidence->addAttachment(KCalendarCore::Attachment(text)); return; } KContacts::Addressee::List list; if (KContacts::VCardDrag::fromMimeData(md, list)) { for (const KContacts::Addressee &addressee : qAsConst(list)) { QString em(addressee.fullEmail()); if (em.isEmpty()) { em = addressee.realName(); } addAttendee(em); } } } QList<AgendaItem::QPtr> &AgendaItem::conflictItems() { return mConflictItems; } void AgendaItem::setConflictItems(const QList<AgendaItem::QPtr> &ci) { mConflictItems = ci; for (QList<AgendaItem::QPtr>::iterator it = mConflictItems.begin(), end(mConflictItems.end()); it != end; ++it) { (*it)->addConflictItem(this); } } void AgendaItem::addConflictItem(const AgendaItem::QPtr &ci) { if (!mConflictItems.contains(ci)) { mConflictItems.append(ci); } } QString AgendaItem::label() const { return mLabelText; } bool AgendaItem::overlaps(CellItem *o) const { AgendaItem::QPtr other = static_cast<AgendaItem *>(o); if (cellXLeft() <= other->cellXRight() && cellXRight() >= other->cellXLeft()) { if ((cellYTop() <= other->cellYBottom()) && (cellYBottom() >= other->cellYTop())) { return true; } } return false; } static void conditionalPaint(QPainter *p, bool condition, int &x, int y, int ft, const QPixmap &pxmp) { if (condition) { p->drawPixmap(x, y, pxmp); x += pxmp.width() + ft; } } void AgendaItem::paintIcon(QPainter *p, int &x, int y, int ft) { QString iconName; if (mIncidence->customProperty("KABC", "ANNIVERSARY") == QLatin1String("YES")) { mSpecialEvent = true; iconName = QStringLiteral("view-calendar-wedding-anniversary"); } else if (mIncidence->customProperty("KABC", "BIRTHDAY") == QLatin1String("YES")) { mSpecialEvent = true; // We don't draw icon. The icon is drawn already, because it's the Akonadi::Collection's icon } conditionalPaint(p, !iconName.isEmpty(), x, y, ft, cachedSmallIcon(iconName)); } void AgendaItem::paintIcons(QPainter *p, int &x, int y, int ft) { if (!mEventView->preferences()->enableAgendaItemIcons()) { return; } paintIcon(p, x, y, ft); QSet<EventView::ItemIcon> icons = mEventView->preferences()->agendaViewIcons(); if (icons.contains(EventViews::EventView::CalendarCustomIcon)) { const QString iconName = mCalendar->iconForIncidence(mIncidence); if (!iconName.isEmpty() && iconName != QLatin1String("view-calendar") && iconName != QLatin1String("office-calendar")) { conditionalPaint(p, true, x, y, ft, QIcon::fromTheme(iconName).pixmap(16, 16)); } } const bool isTodo = mIncidence && mIncidence->type() == Incidence::TypeTodo; if (isTodo && icons.contains(EventViews::EventView::TaskIcon)) { const QString iconName = mIncidence->iconName(mOccurrenceDateTime.toLocalTime()); conditionalPaint(p, !mSpecialEvent, x, y, ft, QIcon::fromTheme(iconName).pixmap(16, 16)); } if (icons.contains(EventView::RecurringIcon)) { conditionalPaint(p, mIconRecur && !mSpecialEvent, x, y, ft, *recurPxmp); } if (icons.contains(EventView::ReminderIcon)) { conditionalPaint(p, mIconAlarm && !mSpecialEvent, x, y, ft, *alarmPxmp); } if (icons.contains(EventView::ReadOnlyIcon)) { conditionalPaint(p, mIconReadonly && !mSpecialEvent, x, y, ft, *readonlyPxmp); } if (icons.contains(EventView::ReplyIcon)) { conditionalPaint(p, mIconReply, x, y, ft, *replyPxmp); } if (icons.contains(EventView::AttendingIcon)) { conditionalPaint(p, mIconGroup, x, y, ft, *groupPxmp); } if (icons.contains(EventView::TentativeIcon)) { conditionalPaint(p, mIconGroupTent, x, y, ft, *groupPxmpTent); } if (icons.contains(EventView::OrganizerIcon)) { conditionalPaint(p, mIconOrganizer, x, y, ft, *organizerPxmp); } } void AgendaItem::paintEvent(QPaintEvent *ev) { if (!mValid) { return; } QRect visRect = visibleRegion().boundingRect(); // when scrolling horizontally in the side-by-side view, the repainted area is clipped // to the newly visible area, which is a problem since the content changes when visRect // changes, so repaint the full item in that case if (ev->rect() != visRect && visRect.isValid() && ev->rect().isValid()) { update(visRect); return; } QPainter p(this); p.setRenderHint(QPainter::Antialiasing); const int fmargin = 0; // frame margin const int ft = 1; // frame thickness for layout, see drawRoundedRect(), // keep multiple of 2 const int margin = 5 + ft + fmargin; // frame + space between frame and content // General idea is to always show the icons (even in the all-day events). // This creates a consistent feeling for the user when the view mode // changes and therefore the available width changes. // Also look at #17984 if (!alarmPxmp) { alarmPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("task-reminder")).pixmap(16, 16)); recurPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("appointment-recurring")).pixmap(16, 16)); readonlyPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("object-locked")).pixmap(16, 16)); replyPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("mail-reply-sender")).pixmap(16, 16)); groupPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("meeting-attending")).pixmap(16, 16)); groupPxmpTent = new QPixmap(QIcon::fromTheme(QStringLiteral( "meeting-attending-tentative")).pixmap(16, 16)); organizerPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("meeting-organizer")).pixmap(16, 16)); } const auto categoryColor = getCategoryColor(); const auto resourceColor = mResourceColor.isValid() ? mResourceColor : categoryColor; const auto frameColor = getFrameColor(resourceColor, categoryColor); const auto bgBaseColor = getBackgroundColor(resourceColor, categoryColor); const auto bgColor = mSelected ? bgBaseColor.lighter(EventView::BRIGHTNESS_FACTOR) : bgBaseColor; const auto textColor = EventViews::getTextColor(bgColor); p.setPen(textColor); p.setFont(mEventView->preferences()->agendaViewFont()); QFontMetrics fm = p.fontMetrics(); const int singleLineHeight = fm.boundingRect(mLabelText).height(); const bool roundTop = !prevMultiItem(); const bool roundBottom = !nextMultiItem(); drawRoundedRect(&p, QRect(fmargin, fmargin, width() - fmargin * 2, height() - fmargin * 2), mSelected, bgColor, true, ft, roundTop, roundBottom); // calculate the height of the full version (case 4) to test whether it is // possible QString shortH; QString longH; if (!isMultiItem()) { shortH = QLocale().toString(mIncidence->dateTime( KCalendarCore::Incidence::RoleDisplayStart).toLocalTime().time(), QLocale::ShortFormat); if (CalendarSupport::hasEvent(mIncidence)) { longH = i18n("%1 - %2", shortH, QLocale().toString(mIncidence->dateTime(KCalendarCore::Incidence::RoleEnd). toLocalTime().time(), QLocale::ShortFormat)); } else { longH = shortH; } } else if (!mMultiItemInfo->mFirstMultiItem) { shortH = QLocale().toString(mIncidence->dtStart().toLocalTime().time(), QLocale::ShortFormat); longH = shortH; } else { shortH = QLocale().toString(mIncidence->dateTime( KCalendarCore::Incidence::RoleEnd).toLocalTime().time(), QLocale::ShortFormat); longH = i18n("- %1", shortH); } KWordWrap ww = KWordWrap::formatText( fm, QRect(0, 0, width() - (2 * margin), -1), 0, mLabelText); int th = ww.boundingRect().height(); int hlHeight = qMax(fm.boundingRect(longH).height(), qMax(alarmPxmp->height(), qMax(recurPxmp->height(), qMax(readonlyPxmp->height(), qMax(replyPxmp->height(), qMax(groupPxmp->height(), organizerPxmp->height())))))); const bool completelyRenderable = th < (height() - 2 * ft - 2 - hlHeight); // case 1: do not draw text when not even a single line fits // Don't do this any more, always try to print out the text. // Even if it's just a few pixel, one can still guess the whole // text from just four pixels' height! if ( //( singleLineHeight > height() - 4 ) || (width() < 16)) { int x = qRound((width() - 16) / 2.0); paintIcon(&p, x /*by-ref*/, margin, ft); return; } // case 2: draw a single line when no more space if ((2 * singleLineHeight) > (height() - 2 * margin)) { int x = margin, txtWidth; if (mIncidence->allDay()) { x += visRect.left(); const int y = qRound((height() - 16) / 2.0); paintIcons(&p, x, y, ft); txtWidth = visRect.right() - margin - x; } else { const int y = qRound((height() - 16) / 2.0); paintIcons(&p, x, y, ft); txtWidth = width() - margin - x; } const int y = ((height() - singleLineHeight) / 2) + fm.ascent(); KWordWrap::drawFadeoutText(&p, x, y, txtWidth, mLabelText); return; } // case 3: enough for 2-5 lines, but not for the header. // Also used for the middle days in multi-events if (((!completelyRenderable) && ((height() - (2 * margin)) <= (5 * singleLineHeight))) || (isMultiItem() && mMultiItemInfo->mNextMultiItem && mMultiItemInfo->mFirstMultiItem)) { int x = margin, txtWidth; if (mIncidence->allDay()) { x += visRect.left(); paintIcons(&p, x, margin, ft); txtWidth = visRect.right() - margin - x; } else { paintIcons(&p, x, margin, ft); txtWidth = width() - margin - x; } ww = KWordWrap::formatText( fm, QRect(0, 0, txtWidth, (height() - (2 * margin))), 0, mLabelText); ww.drawText(&p, x, margin, Qt::AlignHCenter | KWordWrap::FadeOut); return; } // case 4: paint everything, with header: // consists of (vertically) ft + headline&icons + ft + text + margin int y = 2 * ft + hlHeight; if (completelyRenderable) { y += (height() - (2 * ft) - margin - hlHeight - th) / 2; } int x = margin, txtWidth, hTxtWidth, eventX; if (mIncidence->allDay()) { shortH.clear(); longH.clear(); if (const KCalendarCore::Event::Ptr event = CalendarSupport::event(mIncidence)) { if (event->isMultiDay(QTimeZone::systemTimeZone())) { // multi-day, all-day event shortH = i18n("%1 - %2", QLocale().toString(mIncidence->dtStart().toLocalTime().date()), QLocale().toString(mIncidence->dateTime(KCalendarCore::Incidence::RoleEnd). toLocalTime().date())); longH = shortH; // paint headline drawRoundedRect( &p, QRect(fmargin, fmargin, width() - fmargin * 2, -fmargin * 2 + margin + hlHeight), mSelected, frameColor, false, ft, roundTop, false); } else { // single-day, all-day event // paint headline drawRoundedRect( &p, QRect(fmargin, fmargin, width() - fmargin * 2, -fmargin * 2 + margin + hlHeight), mSelected, frameColor, false, ft, roundTop, false); } } else { // to-do // paint headline drawRoundedRect( &p, QRect(fmargin, fmargin, width() - fmargin * 2, -fmargin * 2 + margin + hlHeight), mSelected, frameColor, false, ft, roundTop, false); } x += visRect.left(); eventX = x; txtWidth = visRect.right() - margin - x; paintIcons(&p, x, margin / 2, ft); hTxtWidth = visRect.right() - margin - x; } else { // paint headline drawRoundedRect( &p, QRect(fmargin, fmargin, width() - fmargin * 2, -fmargin * 2 + margin + hlHeight), mSelected, frameColor, false, ft, roundTop, false); txtWidth = width() - margin - x; eventX = x; paintIcons(&p, x, margin / 2, ft); hTxtWidth = width() - margin - x; } QString headline; int hw = fm.boundingRect(longH).width(); if (hw > hTxtWidth) { headline = shortH; hw = fm.boundingRect(shortH).width(); if (hw < txtWidth) { x += (hTxtWidth - hw) / 2; } } else { headline = longH; x += (hTxtWidth - hw) / 2; } p.setBackground(QBrush(frameColor)); p.setPen(EventViews::getTextColor(frameColor)); KWordWrap::drawFadeoutText(&p, x, (margin + hlHeight + fm.ascent()) / 2 - 2, hTxtWidth, headline); // draw event text ww = KWordWrap::formatText( fm, QRect(0, 0, txtWidth, height() - margin - y), 0, mLabelText); p.setBackground(QBrush(bgColor)); p.setPen(textColor); QString ws = ww.wrappedString(); if (ws.leftRef(ws.length() - 1).indexOf(QLatin1Char('\n')) >= 0) { ww.drawText(&p, eventX, y, Qt::AlignLeft | KWordWrap::FadeOut); } else { ww.drawText(&p, eventX + (txtWidth - ww.boundingRect().width() - 2 * margin) / 2, y, Qt::AlignHCenter | KWordWrap::FadeOut); } } void AgendaItem::drawRoundedRect(QPainter *p, const QRect &rect, bool selected, const QColor &bgColor, bool frame, int ft, bool roundTop, bool roundBottom) { Q_UNUSED(ft); if (!mValid) { return; } QPainterPath path; const int RECT_MARGIN = 2; const int RADIUS = 2; // absolute radius const QRect rectWithMargin(rect.x() + RECT_MARGIN, rect.y() + RECT_MARGIN, rect.width() - 2 * RECT_MARGIN, rect.height() - 2 * RECT_MARGIN); const QPoint pointLeftTop(rectWithMargin.x(), rectWithMargin.y()); const QPoint pointRightTop(rectWithMargin.x() + rectWithMargin.width(), rectWithMargin.y()); const QPoint pointLeftBottom(rectWithMargin.x(), rectWithMargin.y() + rectWithMargin.height()); const QPoint pointRightBottom(rectWithMargin.x() + rectWithMargin.width(), rectWithMargin.y() + rectWithMargin.height()); if (!roundTop && !roundBottom) { path.addRect(rectWithMargin); } else if (roundTop && roundBottom) { path.addRoundedRect(rectWithMargin, RADIUS, RADIUS, Qt::AbsoluteSize); } else if (roundTop) { path.moveTo(pointRightBottom); path.lineTo(pointLeftBottom); path.lineTo(QPoint(pointLeftTop.x(), pointLeftTop.y() + RADIUS)); path.quadTo(pointLeftTop, QPoint(pointLeftTop.x() + RADIUS, pointLeftTop.y())); path.lineTo(QPoint(pointRightTop.x() - RADIUS, pointRightTop.y())); path.quadTo(pointRightTop, QPoint(pointRightTop.x(), pointRightTop.y() + RADIUS)); path.lineTo(pointRightBottom); } else if (roundBottom) { path.moveTo(pointRightTop); path.lineTo(QPoint(pointRightBottom.x(), pointRightBottom.y() - RADIUS)); path.quadTo(pointRightBottom, QPoint(pointRightBottom.x() - RADIUS, pointRightBottom.y())); path.lineTo(QPoint(pointLeftBottom.x() + RADIUS, pointLeftBottom.y())); path.quadTo(pointLeftBottom, QPoint(pointLeftBottom.x(), pointLeftBottom.y() - RADIUS)); path.lineTo(pointLeftTop); path.lineTo(pointRightTop); } path.closeSubpath(); p->save(); p->setRenderHint(QPainter::Antialiasing, false); const QPen border(QBrush(QColor(200, 200, 200, 255)),1.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); p->setPen(border); // header if (!frame) { QBrush brushSolid(Qt::SolidPattern); QColor top = bgColor.darker(250); top.setAlpha(selected ? 40 : 60); brushSolid.setColor(top); p->setBrush(bgColor); p->drawPath(path); p->setBrush(brushSolid); p->drawPath(path); p->restore(); return; } p->setBrush(bgColor); p->drawPath(path); p->restore(); } QColor AgendaItem::getCategoryColor() const { const QStringList &categories = mIncidence->categories(); if (categories.isEmpty() || !CalendarSupport::KCalPrefs::instance()->hasCategoryColor(categories.first())) { const auto colorPreference = mEventView->preferences()->agendaViewColors(); if (colorPreference == PrefsBase::CategoryOnly || !mResourceColor.isValid()) { return CalendarSupport::KCalPrefs::instance()->unsetCategoryColor(); } return mResourceColor; } return CalendarSupport::KCalPrefs::instance()->categoryColor(categories.first()); } QColor AgendaItem::getFrameColor(const QColor &resourceColor, const QColor &categoryColor) const { const auto colorPreference = mEventView->preferences()->agendaViewColors(); const bool frameDisplaysCategory = (colorPreference == PrefsBase::CategoryOnly || colorPreference == PrefsBase::ResourceInsideCategoryOutside); return frameDisplaysCategory ? categoryColor : resourceColor; } QColor AgendaItem::getBackgroundColor(const QColor &resourceColor, const QColor &categoryColor) const { if (CalendarSupport::hasTodo(mIncidence) && !mEventView->preferences()->todosUseCategoryColors()) { Todo::Ptr todo = CalendarSupport::todo(mIncidence); Q_ASSERT(todo); const QDate dueDate = todo->dtDue().toLocalTime().date(); const QDate today = QDate::currentDate(); const QDate occurrenceDate = this->occurrenceDate(); if (todo->isOverdue() && today >= occurrenceDate) { return mEventView->preferences()->todoOverdueColor(); } else if (dueDate == today && dueDate == occurrenceDate) { return mEventView->preferences()->todoDueTodayColor(); } } const auto colorPreference = mEventView->preferences()->agendaViewColors(); const bool bgDisplaysCategory = (colorPreference == PrefsBase::CategoryOnly || colorPreference == PrefsBase::CategoryInsideResourceOutside); return bgDisplaysCategory ? categoryColor : resourceColor; } bool AgendaItem::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::Paint) { return mValid; } else { // standard event processing return QObject::eventFilter(obj, event); } } bool AgendaItem::event(QEvent *event) { if (event->type() == QEvent::ToolTip) { if (!mEventView->preferences()->enableToolTips()) { return true; } else if (mValid) { QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event); QToolTip::showText( helpEvent->globalPos(), KCalUtils::IncidenceFormatter::toolTipStr( mCalendar->displayName(mIncidence), mIncidence, occurrenceDate(), true), this); } } return QWidget::event(event); } 0707010000000E000081A40000000200000002000000015F0BF3C900002771000000000000000000000000000000000000004600000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/agendaitem.h/* Copyright (c) 2000,2001,2003 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_AGENDAITEM_H #define EVENTVIEWS_AGENDAITEM_H #include "viewcalendar.h" #include <CalendarSupport/CellItem> #include <Akonadi/Calendar/ETMCalendar> #include <AkonadiCore/Item> #include <QDateTime> #include <QWidget> #include <QPointer> namespace EventViews { class AgendaItem; class EventView; struct MultiItemInfo { int mStartCellXLeft, mStartCellXRight; int mStartCellYTop, mStartCellYBottom; QPointer<AgendaItem> mFirstMultiItem; QPointer<AgendaItem> mPrevMultiItem; QPointer<AgendaItem> mNextMultiItem; QPointer<AgendaItem> mLastMultiItem; }; /** @class AgendaItem @brief This class describes the widgets that represent the various calendar items in the agenda view The AgendaItem has to make sure that it receives all mouse events, which are to be used for dragging and resizing. That means it has to be installed as event filter for its children, if it has children, and it has to pass mouse events from the children to itself. See eventFilter(). Some comments on the movement of multi-day items: Basically, the agenda items are arranged in two implicit double-linked lists. The mMultiItemInfo works like before to describe the currently viewed multi-item. When moving, new events might need to be added to the beginning or the end of the multi-item sequence, or events might need to be hidden. I cannot just delete this items, since I have to restore/show them if the move is reset (i.e. if a drag started). So internally, I keep another doubly-linked list which is longer than the one defined by mMultiItemInfo, but includes the multi-item sequence, too. The mStartMoveInfo stores the first and last item of the multi-item sequence when the move started. The prev and next members of mStartMoveInfo are used for that longer sequence including all (shown and hidden) items. */ class AgendaItem : public QWidget, public CalendarSupport::CellItem { Q_OBJECT public: typedef QPointer<AgendaItem> QPtr; typedef QList<QPtr> List; AgendaItem(EventView *eventView, const MultiViewCalendar::Ptr &calendar, const KCalendarCore::Incidence::Ptr &incidence, int itemPos, int itemCount, const QDateTime &qd, bool isSelected, QWidget *parent); ~AgendaItem() override; Q_REQUIRED_RESULT int cellXLeft() const { return mCellXLeft; } Q_REQUIRED_RESULT int cellXRight() const { return mCellXRight; } Q_REQUIRED_RESULT int cellYTop() const { return mCellYTop; } Q_REQUIRED_RESULT int cellYBottom() const { return mCellYBottom; } Q_REQUIRED_RESULT int cellHeight() const; Q_REQUIRED_RESULT int cellWidth() const; Q_REQUIRED_RESULT int itemPos() const { return mItemPos; } Q_REQUIRED_RESULT int itemCount() const { return mItemCount; } void setCellXY(int X, int YTop, int YBottom); void setCellY(int YTop, int YBottom); void setCellX(int XLeft, int XRight); void setCellXRight(int XRight); /** Start movement */ void startMove(); /** Reset to original values */ void resetMove(); /** End the movement (i.e. clean up) */ void endMove(); void moveRelative(int dx, int dy); /** * Expands the item's top. * * @param dy delta y, number of units to be added to mCellYTop * @param allowOverLimit If false, the new mCellYTop can't be bigger than * mCellYBottom, instead, it gets mCellYBottom's value. * If true, @p dy is always added, regardless if mCellYTop * becomes bigger than mCellYBottom, this is useful when * moving items because it guarantees expandTop and the * following expandBottom call add the same value. */ void expandTop(int dy, const bool allowOverLimit = false); void expandBottom(int dy); void expandLeft(int dx); void expandRight(int dx); Q_REQUIRED_RESULT bool isMultiItem() const; AgendaItem::QPtr prevMoveItem() const { return (mStartMoveInfo) ? (mStartMoveInfo->mPrevMultiItem) : nullptr; } AgendaItem::QPtr nextMoveItem() const { return (mStartMoveInfo) ? (mStartMoveInfo->mNextMultiItem) : nullptr; } MultiItemInfo *moveInfo() const { return mStartMoveInfo; } void setMultiItem(const AgendaItem::QPtr &first, const AgendaItem::QPtr &prev, const AgendaItem::QPtr &next, const AgendaItem::QPtr &last); AgendaItem::QPtr prependMoveItem(const AgendaItem::QPtr &); AgendaItem::QPtr appendMoveItem(const AgendaItem::QPtr &); AgendaItem::QPtr removeMoveItem(const AgendaItem::QPtr &); AgendaItem::QPtr firstMultiItem() const { return (mMultiItemInfo) ? (mMultiItemInfo->mFirstMultiItem) : nullptr; } AgendaItem::QPtr prevMultiItem() const { return (mMultiItemInfo) ? (mMultiItemInfo->mPrevMultiItem) : nullptr; } AgendaItem::QPtr nextMultiItem() const { return (mMultiItemInfo) ? (mMultiItemInfo->mNextMultiItem) : nullptr; } AgendaItem::QPtr lastMultiItem() const { return (mMultiItemInfo) ? (mMultiItemInfo->mLastMultiItem) : nullptr; } Q_REQUIRED_RESULT bool dissociateFromMultiItem(); void setIncidence(const KCalendarCore::Incidence::Ptr &incidence); const KCalendarCore::Incidence::Ptr &incidence() const { return mIncidence; } Q_REQUIRED_RESULT QDateTime occurrenceDateTime() const { return mOccurrenceDateTime; } Q_REQUIRED_RESULT QDate occurrenceDate() const; // /** Update the date of this item's occurrence (not in the event) */ void setOccurrenceDateTime(const QDateTime &qd); void setText(const QString &text) { mLabelText = text; } Q_REQUIRED_RESULT QString text() const { return mLabelText; } QList<AgendaItem::QPtr> &conflictItems(); void setConflictItems(const QList<AgendaItem::QPtr> &); void addConflictItem(const AgendaItem::QPtr &ci); QString label() const override; /** Tells whether this item overlaps item @p o */ bool overlaps(CellItem *o) const override; void setResourceColor(const QColor &color) { mResourceColor = color; } Q_REQUIRED_RESULT QColor resourceColor() const { return mResourceColor; } Q_SIGNALS: void removeAgendaItem(const AgendaItem::QPtr &); void showAgendaItem(const AgendaItem::QPtr &); public Q_SLOTS: void updateIcons(); void select(bool selected = true); void addAttendee(const QString &); protected: bool eventFilter(QObject *obj, QEvent *event) override; bool event(QEvent *event) override; void dragEnterEvent(QDragEnterEvent *e) override; void dropEvent(QDropEvent *e) override; void paintEvent(QPaintEvent *e) override; /** private movement functions. startMove needs to be called of only one of * the multitems. it will then loop through the whole series using * startMovePrivate. Same for resetMove and endMove */ void startMovePrivate(); void resetMovePrivate(); void endMovePrivate(); // Variables to remember start position MultiItemInfo *mStartMoveInfo = nullptr; //Color of the resource QColor mResourceColor; private: void paintIcon(QPainter *p, int &x, int y, int ft); // paint all visible icons void paintIcons(QPainter *p, int &x, int y, int ft); void drawRoundedRect(QPainter *p, const QRect &rect, bool selected, const QColor &bgcolor, bool frame, int ft, bool roundTop, bool roundBottom); Q_REQUIRED_RESULT QColor getCategoryColor() const; Q_REQUIRED_RESULT QColor getFrameColor(const QColor &resourceColor, const QColor &categoryColor) const; Q_REQUIRED_RESULT QColor getBackgroundColor(const QColor &resourceColor, const QColor &categoryColor) const; int mCellXLeft, mCellXRight; int mCellYTop, mCellYBottom; EventView *mEventView = nullptr; MultiViewCalendar::Ptr mCalendar; KCalendarCore::Incidence::Ptr mIncidence; QDateTime mOccurrenceDateTime; bool mValid; bool mCloned; QString mLabelText; bool mSelected; bool mIconAlarm, mIconRecur, mIconReadonly; bool mIconReply, mIconGroup, mIconGroupTent; bool mIconOrganizer, mSpecialEvent; // For incidences that expand through more than 1 day // Will be 1 for single day incidences int mItemPos; int mItemCount; // Multi item pointers MultiItemInfo *mMultiItemInfo = nullptr; QList<AgendaItem::QPtr> mConflictItems; static QPixmap *alarmPxmp; static QPixmap *recurPxmp; static QPixmap *readonlyPxmp; static QPixmap *replyPxmp; static QPixmap *groupPxmp; static QPixmap *groupPxmpTent; static QPixmap *organizerPxmp; static QPixmap *eventPxmp; static QPixmap *todoPxmp; static QPixmap *completedPxmp; }; } #endif 0707010000000F000081A40000000200000002000000015F0BF3C90001481C000000000000000000000000000000000000004800000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/agendaview.cpp/* Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com Author: Sergio Martins, sergio.martins@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "agendaview.h" #include "agenda.h" #include "agendaitem.h" #include "viewcalendar.h" #include "alternatelabel.h" #include "calendardecoration.h" #include "decorationlabel.h" #include "prefs.h" #include "timelabels.h" #include "timelabelszone.h" #include "calendarview_debug.h" #include <Akonadi/Calendar/ETMCalendar> #include <CalendarSupport/CollectionSelection> #include <Akonadi/Calendar/IncidenceChanger> #include <CalendarSupport/Utils> #include <CalendarSupport/KCalPrefs> #include <KCalendarCore/CalFilter> #include <KCalendarCore/CalFormat> #include <KCalendarCore/OccurrenceIterator> #include <KIconLoader> // for SmallIcon() #include <KMessageBox> #include <KServiceTypeTrader> #include <KWordWrap> #include <QApplication> #include <QDrag> #include <QGridLayout> #include <QLabel> #include <QPainter> #include <QScrollBar> #include <QSplitter> #include <QStyle> #include <QTimer> #include <KLocalizedString> #include <vector> using namespace EventViews; enum { SPACING = 2 }; enum { SHRINKDOWN = 2 // points less for the timezone font }; class Q_DECL_HIDDEN EventIndicator::Private { public: Private(EventIndicator *parent, EventIndicator::Location loc) : q(parent) , mColumns(1) , mLocation(loc) { mEnabled.resize(mColumns); QChar ch; // Dashed up and down arrow characters ch = QChar(mLocation == Top ? 0x21e1 : 0x21e3); QFont font = q->font(); font.setPointSize(KIconLoader::global()->currentSize(KIconLoader::Dialog)); QFontMetrics fm(font); QRect rect = fm.boundingRect(ch).adjusted(-2, -2, 2, 2); mPixmap = QPixmap(rect.size()); mPixmap.fill(Qt::transparent); QPainter p(&mPixmap); p.setOpacity(0.33); p.setFont(font); p.setPen(q->palette().text().color()); p.drawText(-rect.left(), -rect.top(), ch); } void adjustGeometry() { QRect rect; rect.setWidth(q->parentWidget()->width()); rect.setHeight(q->height()); rect.setLeft(0); rect.setTop(mLocation == EventIndicator::Top ? 0 : q->parentWidget()->height() - rect.height()); q->setGeometry(rect); } public: int mColumns; Location mLocation; QPixmap mPixmap; QVector<bool> mEnabled; private: EventIndicator *const q; }; EventIndicator::EventIndicator(Location loc, QWidget *parent) : QFrame(parent) , d(new Private(this, loc)) { setAttribute(Qt::WA_TransparentForMouseEvents); setFixedHeight(d->mPixmap.height()); parent->installEventFilter(this); } EventIndicator::~EventIndicator() { delete d; } void EventIndicator::paintEvent(QPaintEvent *) { QPainter painter(this); const double cellWidth = static_cast<double>(width()) / d->mColumns; const bool isRightToLeft = QApplication::isRightToLeft(); const uint pixmapOffset = isRightToLeft ? 0 : (cellWidth - d->mPixmap.width()); for (int i = 0; i < d->mColumns; ++i) { if (d->mEnabled[ i ]) { const int xOffset = (isRightToLeft ? (d->mColumns - 1 - i) : i) * cellWidth; painter.drawPixmap(xOffset + pixmapOffset, 0, d->mPixmap); } } } bool EventIndicator::eventFilter(QObject *, QEvent *event) { if (event->type() == QEvent::Resize) { d->adjustGeometry(); } return false; } void EventIndicator::changeColumns(int columns) { d->mColumns = columns; d->mEnabled.resize(d->mColumns); show(); raise(); update(); } void EventIndicator::enableColumn(int column, bool enable) { Q_ASSERT(column < d->mEnabled.count()); d->mEnabled[ column ] = enable; } //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// class AgendaView::Private : public Akonadi::ETMCalendar::CalendarObserver { AgendaView *const q; public: explicit Private(AgendaView *parent, bool isInteractive, bool isSideBySide) : q(parent) , mTopDayLabels(nullptr) , mLayoutTopDayLabels(nullptr) , mTopDayLabelsFrame(nullptr) , mLayoutBottomDayLabels(nullptr) , mBottomDayLabels(nullptr) , mBottomDayLabelsFrame(nullptr) , mTimeBarHeaderFrame(nullptr) , mAllDayAgenda(nullptr) , mAgenda(nullptr) , mTimeLabelsZone(nullptr) , mAllowAgendaUpdate(true) , mUpdateItem(0) , mIsSideBySide(isSideBySide) , mDummyAllDayLeft(nullptr) , mUpdateAllDayAgenda(true) , mUpdateAgenda(true) , mIsInteractive(isInteractive) , mUpdateEventIndicatorsScheduled(false) , mViewCalendar(MultiViewCalendar::Ptr(new MultiViewCalendar())) { mViewCalendar->mAgendaView = q; mViewCalendar->setETMCalendar(q->calendar()); } public: // view widgets QGridLayout *mGridLayout = nullptr; QFrame *mTopDayLabels = nullptr; QBoxLayout *mLayoutTopDayLabels = nullptr; QFrame *mTopDayLabelsFrame = nullptr; QList<AlternateLabel *> mDateDayLabels; QBoxLayout *mLayoutBottomDayLabels = nullptr; QFrame *mBottomDayLabels = nullptr; QFrame *mBottomDayLabelsFrame = nullptr; QFrame *mAllDayFrame = nullptr; QWidget *mTimeBarHeaderFrame = nullptr; QSplitter *mSplitterAgenda = nullptr; QList<QLabel *> mTimeBarHeaders; Agenda *mAllDayAgenda = nullptr; Agenda *mAgenda = nullptr; TimeLabelsZone *mTimeLabelsZone = nullptr; KCalendarCore::DateList mSelectedDates; // List of dates to be displayed KCalendarCore::DateList mSaveSelectedDates; // Save the list of dates between updateViews int mViewType; EventIndicator *mEventIndicatorTop = nullptr; EventIndicator *mEventIndicatorBottom = nullptr; QVector<int> mMinY; QVector<int> mMaxY; QVector<bool> mHolidayMask; QDateTime mTimeSpanBegin; QDateTime mTimeSpanEnd; bool mTimeSpanInAllDay; bool mAllowAgendaUpdate; Akonadi::Item mUpdateItem; const bool mIsSideBySide; QWidget *mDummyAllDayLeft = nullptr; bool mUpdateAllDayAgenda; bool mUpdateAgenda; bool mIsInteractive; bool mUpdateEventIndicatorsScheduled; // Contains days that have at least one all-day Event with TRANSP: OPAQUE (busy) // that has you as organizer or attendee so we can color background with a different // color QMap<QDate, KCalendarCore::Event::List > mBusyDays; EventViews::MultiViewCalendar::Ptr mViewCalendar; bool makesWholeDayBusy(const KCalendarCore::Incidence::Ptr &incidence) const; CalendarDecoration::Decoration *loadCalendarDecoration(const QString &name); void clearView(); void setChanges(EventView::Changes changes, const KCalendarCore::Incidence::Ptr &incidence = KCalendarCore::Incidence::Ptr()); /** Returns a list of consecutive dates, starting with @p start and ending with @p end. If either start or end are invalid, a list with QDate::currentDate() is returned */ static QList<QDate> generateDateList(const QDate &start, const QDate &end); void changeColumns(int numColumns); AgendaItem::List agendaItems(const QString &uid) const; // insertAtDateTime is in the view's timezone void insertIncidence(const KCalendarCore::Incidence::Ptr &, const QDateTime &recurrenceId, const QDateTime &insertAtDateTime, bool createSelected); void reevaluateIncidence(const KCalendarCore::Incidence::Ptr &incidence); bool datesEqual(const KCalendarCore::Incidence::Ptr &one, const KCalendarCore::Incidence::Ptr &two) const; /** * Returns false if the incidence is for sure outside of the visible timespan. * Returns true if it might be, meaning that to be sure, timezones must be * taken into account. * This is a very fast way of discarding incidences that are outside of the * timespan and only performing expensive timezone operations on the ones * that might be viisble */ bool mightBeVisible(const KCalendarCore::Incidence::Ptr &incidence) const; protected: /* reimplemented from KCalendarCore::Calendar::CalendarObserver */ void calendarIncidenceAdded(const KCalendarCore::Incidence::Ptr &incidence) override; void calendarIncidenceChanged(const KCalendarCore::Incidence::Ptr &incidence) override; void calendarIncidenceDeleted(const KCalendarCore::Incidence::Ptr &incidence, const KCalendarCore::Calendar *calendar) override; private: //quiet --overloaded-virtual warning using KCalendarCore::Calendar::CalendarObserver::calendarIncidenceDeleted; }; bool AgendaView::Private::datesEqual(const KCalendarCore::Incidence::Ptr &one, const KCalendarCore::Incidence::Ptr &two) const { const auto start1 = one->dtStart(); const auto start2 = two->dtStart(); const auto end1 = one->dateTime(KCalendarCore::Incidence::RoleDisplayEnd); const auto end2 = two->dateTime(KCalendarCore::Incidence::RoleDisplayEnd); if (start1.isValid() ^ start2.isValid()) { return false; } if (end1.isValid() ^ end2.isValid()) { return false; } if (start1.isValid() && start1 != start2) { return false; } if (end1.isValid() && end1 != end2) { return false; } return true; } AgendaItem::List AgendaView::Private::agendaItems(const QString &uid) const { AgendaItem::List allDayAgendaItems = mAllDayAgenda->agendaItems(uid); return allDayAgendaItems.isEmpty() ? mAgenda->agendaItems(uid) : allDayAgendaItems; } bool AgendaView::Private::mightBeVisible(const KCalendarCore::Incidence::Ptr &incidence) const { KCalendarCore::Todo::Ptr todo = incidence.dynamicCast<KCalendarCore::Todo>(); // KDateTime::toTimeSpec() is expensive, so lets first compare only the date, // to see if the incidence is visible. // If it's more than 48h of diff, then for sure it won't be visible, // independently of timezone. // The largest difference between two timezones is about 24 hours. if (todo && todo->isOverdue()) { // Don't optimize this case. Overdue to-dos have their own rules for displaying themselves return true; } if (!incidence->recurs()) { // If DTEND/DTDUE is before the 1st visible column const QDate tdate = incidence->dateTime(KCalendarCore::Incidence::RoleEnd).date(); if (tdate.daysTo(mSelectedDates.first()) > 2) { return false; } // if DTSTART is after the last visible column if (!todo && mSelectedDates.last().daysTo(incidence->dtStart().date()) > 2) { return false; } // if DTDUE is after the last visible column if (todo && mSelectedDates.last().daysTo(todo->dtDue().date()) > 2) { return false; } } return true; } void AgendaView::Private::changeColumns(int numColumns) { // mMinY, mMaxY and mEnabled must all have the same size. // Make sure you preserve this order because mEventIndicatorTop->changeColumns() // can trigger a lot of stuff, and code will be executed when mMinY wasn't resized yet. mMinY.resize(numColumns); mMaxY.resize(numColumns); mEventIndicatorTop->changeColumns(numColumns); mEventIndicatorBottom->changeColumns(numColumns); } /** static */ QList<QDate> AgendaView::Private::generateDateList(const QDate &start, const QDate &end) { QList<QDate> list; if (start.isValid() && end.isValid() && end >= start && start.daysTo(end) < AgendaView::MAX_DAY_COUNT) { QDate date = start; list.reserve(start.daysTo(end) + 1); while (date <= end) { list.append(date); date = date.addDays(1); } } else { list.append(QDate::currentDate()); } return list; } void AgendaView::Private::reevaluateIncidence(const KCalendarCore::Incidence::Ptr &incidence) { if (!incidence || !mViewCalendar->isValid(incidence)) { qCWarning(CALENDARVIEW_LOG) << "invalid incidence or item not found." << incidence; return; } q->removeIncidence(incidence); q->displayIncidence(incidence, false); mAgenda->checkScrollBoundaries(); q->updateEventIndicators(); } void AgendaView::Private::calendarIncidenceAdded(const KCalendarCore::Incidence::Ptr &incidence) { if (!incidence || !mViewCalendar->isValid(incidence)) { qCCritical(CALENDARVIEW_LOG) << "AgendaView::Private::calendarIncidenceAdded() Invalid incidence or item:" << incidence; Q_ASSERT(false); return; } if (incidence->hasRecurrenceId() && mViewCalendar->isValid(incidence)) { // Reevaluate the main event instead, if it was inserted before this one KCalendarCore::Incidence::Ptr mainIncidence = q->calendar2(incidence)->incidence(incidence->uid()); if (mainIncidence) { reevaluateIncidence(mainIncidence); } } else if (q->displayIncidence(incidence, false)) { mAgenda->checkScrollBoundaries(); q->scheduleUpdateEventIndicators(); } } void AgendaView::Private::calendarIncidenceChanged(const KCalendarCore::Incidence::Ptr &incidence) { if (!incidence || incidence->uid().isEmpty()) { qCCritical(CALENDARVIEW_LOG) << "AgendaView::calendarIncidenceChanged() Invalid incidence or empty UID. " << incidence; Q_ASSERT(false); return; } AgendaItem::List agendaItems = this->agendaItems(incidence->uid()); if (agendaItems.isEmpty()) { qCWarning(CALENDARVIEW_LOG) << "AgendaView::calendarIncidenceChanged() Invalid agendaItem for incidence " << incidence->uid(); return; } // Optimization: If the dates didn't change, just repaint it. // This optimization for now because we need to process collisions between agenda items. if (false && !incidence->recurs() && agendaItems.count() == 1) { KCalendarCore::Incidence::Ptr originalIncidence = agendaItems.first()->incidence(); if (datesEqual(originalIncidence, incidence)) { for (const AgendaItem::QPtr &agendaItem : qAsConst(agendaItems)) { agendaItem->setIncidence(KCalendarCore::Incidence::Ptr(incidence->clone())); agendaItem->update(); } return; } } if (incidence->hasRecurrenceId() && mViewCalendar->isValid(incidence)) { // Reevaluate the main event instead, if it exists KCalendarCore::Incidence::Ptr mainIncidence = q->calendar2(incidence)->incidence(incidence->uid()); reevaluateIncidence(mainIncidence ? mainIncidence : incidence); } else { reevaluateIncidence(incidence); } // No need to call setChanges(), that triggers a fillAgenda() // setChanges(q->changes() | IncidencesEdited, incidence); } void AgendaView::Private::calendarIncidenceDeleted(const KCalendarCore::Incidence::Ptr &incidence, const KCalendarCore::Calendar *calendar) { Q_UNUSED(calendar); if (!incidence || incidence->uid().isEmpty()) { qCWarning(CALENDARVIEW_LOG) << "invalid incidence or empty uid: " << incidence; Q_ASSERT(false); return; } q->removeIncidence(incidence); if (incidence->hasRecurrenceId()) { // Reevaluate the main event, if it exists. The exception was removed so the main recurrent series // will no be bigger. if (mViewCalendar->isValid(incidence->uid())) { KCalendarCore::Incidence::Ptr mainIncidence = q->calendar2(incidence->uid())->incidence( incidence->uid()); if (mainIncidence) { reevaluateIncidence(mainIncidence); } } } else if (mightBeVisible(incidence)) { // No need to call setChanges(), that triggers a fillAgenda() // setChanges(q->changes() | IncidencesDeleted, CalendarSupport::incidence(incidence)); mAgenda->checkScrollBoundaries(); q->scheduleUpdateEventIndicators(); } } void EventViews::AgendaView::Private::setChanges(EventView::Changes changes, const KCalendarCore::Incidence::Ptr &incidence) { // We could just call EventView::setChanges(...) but we're going to do a little // optimization. If only an all day item was changed, only all day agenda // should be updated. // all bits = 1 const int ones = ~0; const int incidenceOperations = IncidencesAdded | IncidencesEdited | IncidencesDeleted; // If changes has a flag turned on, other than incidence operations, then update both agendas if ((ones ^ incidenceOperations) & changes) { mUpdateAllDayAgenda = true; mUpdateAgenda = true; } else if (incidence) { mUpdateAllDayAgenda = mUpdateAllDayAgenda | incidence->allDay(); mUpdateAgenda = mUpdateAgenda | !incidence->allDay(); } q->EventView::setChanges(changes); } void AgendaView::Private::clearView() { if (mUpdateAllDayAgenda) { mAllDayAgenda->clear(); } if (mUpdateAgenda) { mAgenda->clear(); } mBusyDays.clear(); } void AgendaView::Private::insertIncidence(const KCalendarCore::Incidence::Ptr &incidence, const QDateTime &recurrenceId, const QDateTime &insertAtDateTime, bool createSelected) { if (!q->filterByCollectionSelection(incidence)) { return; } KCalendarCore::Event::Ptr event = CalendarSupport::event(incidence); KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(incidence); const QDate insertAtDate = insertAtDateTime.date(); // In case incidence->dtStart() isn't visible (crosses bounderies) const int curCol = qMax(mSelectedDates.first().daysTo(insertAtDate), qint64(0)); // The date for the event is not displayed, just ignore it if (curCol >= mSelectedDates.count()) { return; } if (mMinY.count() <= curCol) { mMinY.resize(mSelectedDates.count()); } if (mMaxY.count() <= curCol) { mMaxY.resize(mSelectedDates.count()); } // Default values, which can never be reached mMinY[curCol] = mAgenda->timeToY(QTime(23, 59)) + 1; mMaxY[curCol] = mAgenda->timeToY(QTime(0, 0)) - 1; int beginX; int endX; if (event) { const QDate firstVisibleDate = mSelectedDates.first(); // its crossing bounderies, lets calculate beginX and endX const int duration = event->dtStart().toLocalTime().daysTo(event->dtEnd()); if (insertAtDate < firstVisibleDate) { beginX = curCol + firstVisibleDate.daysTo(insertAtDate); endX = beginX + duration; } else { beginX = curCol; endX = beginX + duration; } } else if (todo) { if (!todo->hasDueDate()) { return; // todo shall not be displayed if it has no date } beginX = endX = curCol; } else { return; } const QDate today = QDate::currentDate(); if (todo && todo->isOverdue() && today >= insertAtDate) { mAllDayAgenda->insertAllDayItem(incidence, recurrenceId, curCol, curCol, createSelected); } else if (incidence->allDay()) { mAllDayAgenda->insertAllDayItem(incidence, recurrenceId, beginX, endX, createSelected); } else if (event && event->isMultiDay(QTimeZone::systemTimeZone())) { // TODO: We need a better isMultiDay(), one that receives the occurrence. // In the single-day handling code there's a neat comment on why // we're calculating the start time this way const QTime startTime = insertAtDateTime.time(); // In the single-day handling code there's a neat comment on why we use the // duration instead of fetching the end time directly const int durationOfFirstOccurrence = event->dtStart().secsTo(event->dtEnd()); QTime endTime = startTime.addSecs(durationOfFirstOccurrence); const int startY = mAgenda->timeToY(startTime); if (endTime == QTime(0, 0, 0)) { endTime = QTime(23, 59, 59); } const int endY = mAgenda->timeToY(endTime) - 1; if ((beginX <= 0 && curCol == 0) || beginX == curCol) { mAgenda->insertMultiItem(incidence, recurrenceId, beginX, endX, startY, endY, createSelected); } if (beginX == curCol) { mMaxY[curCol] = mAgenda->timeToY(QTime(23, 59)); if (startY < mMinY[curCol]) { mMinY[curCol] = startY; } } else if (endX == curCol) { mMinY[curCol] = mAgenda->timeToY(QTime(0, 0)); if (endY > mMaxY[curCol]) { mMaxY[curCol] = endY; } } else { mMinY[curCol] = mAgenda->timeToY(QTime(0, 0)); mMaxY[curCol] = mAgenda->timeToY(QTime(23, 59)); } } else { int startY = 0, endY = 0; if (event) { // Single day events fall here // Don't use event->dtStart().toTimeSpec(timeSpec).time(). // If it's a UTC recurring event it should have a different time when it crosses DST, // so we must use insertAtDate here, so we get the correct time. // // The nth occurrence doesn't always have the same time as the 1st occurrence. const QTime startTime = insertAtDateTime.time(); // We could just fetch the end time directly from dtEnd() instead of adding a duration to the // start time. This way is best because it preserves the duration of the event. There are some // corner cases where the duration would be messed up, for example a UTC event that when // converted to local has dtStart() in day light saving time, but dtEnd() outside DST. // It could create events with 0 duration. const int durationOfFirstOccurrence = event->dtStart().secsTo(event->dtEnd()); QTime endTime = startTime.addSecs(durationOfFirstOccurrence); startY = mAgenda->timeToY(startTime); if (endTime == QTime(0, 0, 0)) { endTime = QTime(23, 59, 59); } endY = mAgenda->timeToY(endTime) - 1; } if (todo) { QTime t; if (todo->recurs()) { // The time we get depends on the insertAtDate, because of daylight savings changes const QDateTime ocurrrenceDateTime = QDateTime(insertAtDate, todo->dtDue().time(), todo->dtDue().timeZone()); t = ocurrrenceDateTime.toLocalTime().time(); } else { t = todo->dtDue().toLocalTime().time(); } if (t == QTime(0, 0) && !todo->recurs()) { // To-dos due at 00h00 are drawn at the previous day and ending at // 23h59. For recurring to-dos, that's not being done because it wasn't // implemented yet in ::fillAgenda(). t = QTime(23, 59); } const int halfHour = 1800; if (t.addSecs(-halfHour) < t) { startY = mAgenda->timeToY(t.addSecs(-halfHour)); endY = mAgenda->timeToY(t) - 1; } else { startY = 0; endY = mAgenda->timeToY(t.addSecs(halfHour)) - 1; } } if (endY < startY) { endY = startY; } mAgenda->insertItem(incidence, recurrenceId, curCol, startY, endY, 1, 1, createSelected); if (startY < mMinY[curCol]) { mMinY[curCol] = startY; } if (endY > mMaxY[curCol]) { mMaxY[curCol] = endY; } } } //////////////////////////////////////////////////////////////////////////// AgendaView::AgendaView(const QDate &start, const QDate &end, bool isInteractive, bool isSideBySide, QWidget *parent) : EventView(parent) , d(new Private(this, isInteractive, isSideBySide)) { init(start, end); } AgendaView::AgendaView(const PrefsPtr &prefs, const QDate &start, const QDate &end, bool isInteractive, bool isSideBySide, QWidget *parent) : EventView(parent) , d(new Private(this, isInteractive, isSideBySide)) { setPreferences(prefs); init(start, end); } void AgendaView::init(const QDate &start, const QDate &end) { d->mSelectedDates = Private::generateDateList(start, end); d->mGridLayout = new QGridLayout(this); d->mGridLayout->setContentsMargins(0, 0, 0, 0); /* Create agenda splitter */ d->mSplitterAgenda = new QSplitter(Qt::Vertical, this); d->mGridLayout->addWidget(d->mSplitterAgenda, 1, 0); /* Create day name labels for agenda columns */ d->mTopDayLabelsFrame = new QFrame(d->mSplitterAgenda); auto layout = new QHBoxLayout(d->mTopDayLabelsFrame); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(SPACING); /* Create all-day agenda widget */ d->mAllDayFrame = new QFrame(d->mSplitterAgenda); auto allDayFrameLayout = new QHBoxLayout(d->mAllDayFrame); allDayFrameLayout->setContentsMargins(0, 0, 0, 0); allDayFrameLayout->setSpacing(SPACING); // Alignment and description widgets if (!d->mIsSideBySide) { d->mTimeBarHeaderFrame = new QFrame(d->mAllDayFrame); allDayFrameLayout->addWidget(d->mTimeBarHeaderFrame); auto timeBarHeaderFrameLayout = new QHBoxLayout(d->mTimeBarHeaderFrame); timeBarHeaderFrameLayout->setContentsMargins(0, 0, 0, 0); timeBarHeaderFrameLayout->setSpacing(0); d->mDummyAllDayLeft = new QWidget(d->mAllDayFrame); allDayFrameLayout->addWidget(d->mDummyAllDayLeft); } // The widget itself AgendaScrollArea *allDayScrollArea = new AgendaScrollArea(true, this, d->mIsInteractive, d->mAllDayFrame); allDayFrameLayout->addWidget(allDayScrollArea); d->mAllDayAgenda = allDayScrollArea->agenda(); /* Create the main agenda widget and the related widgets */ QWidget *agendaFrame = new QWidget(d->mSplitterAgenda); QHBoxLayout *agendaLayout = new QHBoxLayout(agendaFrame); agendaLayout->setContentsMargins(0, 0, 0, 0); agendaLayout->setSpacing(SPACING); // Create agenda AgendaScrollArea *scrollArea = new AgendaScrollArea(false, this, d->mIsInteractive, agendaFrame); d->mAgenda = scrollArea->agenda(); // Create event indicator bars d->mEventIndicatorTop = new EventIndicator(EventIndicator::Top, scrollArea->viewport()); d->mEventIndicatorBottom = new EventIndicator(EventIndicator::Bottom, scrollArea->viewport()); // Create time labels d->mTimeLabelsZone = new TimeLabelsZone(this, preferences(), d->mAgenda); // This timeLabelsZoneLayout is for adding some spacing // to align timelabels, to agenda's grid QVBoxLayout *timeLabelsZoneLayout = new QVBoxLayout(); agendaLayout->addLayout(timeLabelsZoneLayout); agendaLayout->addWidget(scrollArea); timeLabelsZoneLayout->addSpacing(scrollArea->frameWidth()); timeLabelsZoneLayout->addWidget(d->mTimeLabelsZone); timeLabelsZoneLayout->addSpacing(scrollArea->frameWidth()); // Scrolling connect(d->mAgenda, &Agenda::zoomView, this, &AgendaView::zoomView); // Event indicator updates connect(d->mAgenda, &Agenda::lowerYChanged, this, &AgendaView::updateEventIndicatorTop); connect(d->mAgenda, &Agenda::upperYChanged, this, &AgendaView::updateEventIndicatorBottom); if (d->mIsSideBySide) { d->mTimeLabelsZone->hide(); } /* Create a frame at the bottom which may be used by decorations */ d->mBottomDayLabelsFrame = new QFrame(d->mSplitterAgenda); layout = new QHBoxLayout(d->mBottomDayLabelsFrame); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(SPACING); if (!d->mIsSideBySide) { /* Make the all-day and normal agendas line up with each other */ int margin = style()->pixelMetric(QStyle::PM_ScrollBarExtent); if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) { // Needed for some styles. Oxygen needs it, Plastique does not. margin -= scrollArea->frameWidth(); } d->mAllDayFrame->layout()->addItem(new QSpacerItem(margin, 0)); } updateTimeBarWidth(); // Don't call it now, bottom agenda isn't fully up yet QMetaObject::invokeMethod(this, &AgendaView::alignAgendas, Qt::QueuedConnection); // Whoever changes this code, remember to leave createDayLabels() // inside the ctor, so it's always called before readSettings(), so // readSettings() works on the splitter that has the right amount of // widgets (createDayLabels() via placeDecorationFrame() removes widgets). createDayLabels(true); /* Connect the agendas */ connect(d->mAllDayAgenda, &Agenda::newTimeSpanSignal, this, &AgendaView::newTimeSpanSelectedAllDay); connect(d->mAgenda, &Agenda::newTimeSpanSignal, this, &AgendaView::newTimeSpanSelected); connectAgenda(d->mAgenda, d->mAllDayAgenda); connectAgenda(d->mAllDayAgenda, d->mAgenda); } AgendaView::~AgendaView() { for (const ViewCalendar::Ptr &cal : qAsConst(d->mViewCalendar->mSubCalendars)) { if (cal->getCalendar()) { cal->getCalendar()->unregisterObserver(d); } } delete d; } KCalendarCore::Calendar::Ptr AgendaView::calendar2(const KCalendarCore::Incidence::Ptr &incidence) const { return d->mViewCalendar->findCalendar(incidence)->getCalendar(); } KCalendarCore::Calendar::Ptr AgendaView::calendar2(const QString &incidenceIdentifier) const { return d->mViewCalendar->findCalendar(incidenceIdentifier)->getCalendar(); } void AgendaView::setCalendar(const Akonadi::ETMCalendar::Ptr &cal) { if (calendar()) { calendar()->unregisterObserver(d); } Q_ASSERT(cal); EventView::setCalendar(cal); calendar()->registerObserver(d); d->mViewCalendar->setETMCalendar(cal); d->mAgenda->setCalendar(d->mViewCalendar); d->mAllDayAgenda->setCalendar(d->mViewCalendar); } void AgendaView::addCalendar(const ViewCalendar::Ptr &cal) { d->mViewCalendar->addCalendar(cal); cal->getCalendar()->registerObserver(d); } void AgendaView::connectAgenda(Agenda *agenda, Agenda *otherAgenda) { connect(agenda, &Agenda::showNewEventPopupSignal, this, &AgendaView::showNewEventPopupSignal); connect(agenda, &Agenda::showIncidencePopupSignal, this, &AgendaView::slotShowIncidencePopup); agenda->setCalendar(d->mViewCalendar); connect(agenda, SIGNAL(newEventSignal()), SIGNAL(newEventSignal())); connect(agenda, &Agenda::newStartSelectSignal, otherAgenda, &Agenda::clearSelection); connect(agenda, &Agenda::newStartSelectSignal, this, &AgendaView::timeSpanSelectionChanged); connect(agenda, &Agenda::editIncidenceSignal, this, &AgendaView::slotEditIncidence); connect(agenda, &Agenda::showIncidenceSignal, this, &AgendaView::slotShowIncidence); connect(agenda, &Agenda::deleteIncidenceSignal, this, &AgendaView::slotDeleteIncidence); // drag signals connect(agenda, &Agenda::startDragSignal, this, [this](const KCalendarCore::Incidence::Ptr &ptr) { startDrag(ptr); }); // synchronize selections connect(agenda, &Agenda::incidenceSelected, otherAgenda, &Agenda::deselectItem); connect(agenda, &Agenda::incidenceSelected, this, &AgendaView::slotIncidenceSelected); // rescheduling of todos by d'n'd connect(agenda, QOverload<const KCalendarCore::Incidence::List &, const QPoint &, bool>::of(&Agenda::droppedIncidences), this, QOverload<const KCalendarCore::Incidence::List &, const QPoint &, bool>::of(&AgendaView::slotIncidencesDropped)); connect(agenda, QOverload<const QList<QUrl> &, const QPoint &, bool>::of(&Agenda::droppedIncidences), this, QOverload<const QList<QUrl> &, const QPoint &, bool>::of(&AgendaView::slotIncidencesDropped)); } void AgendaView::slotIncidenceSelected(const KCalendarCore::Incidence::Ptr &incidence, const QDate &date) { Akonadi::Item item = d->mViewCalendar->item(incidence); if (item.isValid()) { Q_EMIT incidenceSelected(item, date); } } void AgendaView::slotShowIncidencePopup(const KCalendarCore::Incidence::Ptr &incidence, const QDate &date) { Akonadi::Item item = d->mViewCalendar->item(incidence); //qDebug() << "wanna see the popup for " << incidence->uid() << item.id(); if (item.isValid()) { Q_EMIT showIncidencePopupSignal(item, date); } } void AgendaView::slotShowIncidence(const KCalendarCore::Incidence::Ptr &incidence) { Akonadi::Item item = d->mViewCalendar->item(incidence); if (item.isValid()) { Q_EMIT showIncidenceSignal(item); } } void AgendaView::slotEditIncidence(const KCalendarCore::Incidence::Ptr &incidence) { Akonadi::Item item = d->mViewCalendar->item(incidence); if (item.isValid()) { Q_EMIT editIncidenceSignal(item); } } void AgendaView::slotDeleteIncidence(const KCalendarCore::Incidence::Ptr &incidence) { Akonadi::Item item = d->mViewCalendar->item(incidence); if (item.isValid()) { Q_EMIT deleteIncidenceSignal(item); } } void AgendaView::zoomInVertically() { if (!d->mIsSideBySide) { preferences()->setHourSize(preferences()->hourSize() + 1); } d->mAgenda->updateConfig(); d->mAgenda->checkScrollBoundaries(); d->mTimeLabelsZone->updateAll(); setChanges(changes() | ZoomChanged); updateView(); } void AgendaView::zoomOutVertically() { if (preferences()->hourSize() > 4 || d->mIsSideBySide) { if (!d->mIsSideBySide) { preferences()->setHourSize(preferences()->hourSize() - 1); } d->mAgenda->updateConfig(); d->mAgenda->checkScrollBoundaries(); d->mTimeLabelsZone->updateAll(); setChanges(changes() | ZoomChanged); updateView(); } } void AgendaView::zoomInHorizontally(const QDate &date) { QDate begin; QDate newBegin; QDate dateToZoom = date; int ndays, count; begin = d->mSelectedDates.first(); ndays = begin.daysTo(d->mSelectedDates.last()); // zoom with Action and are there a selected Incidence?, Yes, I zoom in to it. if (!dateToZoom.isValid()) { dateToZoom = d->mAgenda->selectedIncidenceDate(); } if (!dateToZoom.isValid()) { if (ndays > 1) { newBegin = begin.addDays(1); count = ndays - 1; Q_EMIT zoomViewHorizontally(newBegin, count); } } else { if (ndays <= 2) { newBegin = dateToZoom; count = 1; } else { newBegin = dateToZoom.addDays(-ndays / 2 + 1); count = ndays - 1; } Q_EMIT zoomViewHorizontally(newBegin, count); } } void AgendaView::zoomOutHorizontally(const QDate &date) { QDate begin; QDate newBegin; QDate dateToZoom = date; int ndays, count; begin = d->mSelectedDates.first(); ndays = begin.daysTo(d->mSelectedDates.last()); // zoom with Action and are there a selected Incidence?, Yes, I zoom out to it. if (!dateToZoom.isValid()) { dateToZoom = d->mAgenda->selectedIncidenceDate(); } if (!dateToZoom.isValid()) { newBegin = begin.addDays(-1); count = ndays + 3; } else { newBegin = dateToZoom.addDays(-ndays / 2 - 1); count = ndays + 3; } if (abs(count) >= 31) { qCDebug(CALENDARVIEW_LOG) << "change to the month view?"; } else { //We want to center the date Q_EMIT zoomViewHorizontally(newBegin, count); } } void AgendaView::zoomView(const int delta, const QPoint &pos, const Qt::Orientation orient) { // TODO find out why this is necessary. seems to be some kind of performance hack static QDate zoomDate; static QTimer *t = new QTimer(this); //Zoom to the selected incidence, on the other way // zoom to the date on screen after the first mousewheel move. if (orient == Qt::Horizontal) { const QDate date = d->mAgenda->selectedIncidenceDate(); if (date.isValid()) { zoomDate = date; } else { if (!t->isActive()) { zoomDate = d->mSelectedDates[ pos.x() ]; } t->setSingleShot(true); t->start(1000); } if (delta > 0) { zoomOutHorizontally(zoomDate); } else { zoomInHorizontally(zoomDate); } } else { // Vertical zoom const QPoint posConstentsOld = d->mAgenda->gridToContents(pos); if (delta > 0) { zoomOutVertically(); } else { zoomInVertically(); } const QPoint posConstentsNew = d->mAgenda->gridToContents(pos); d->mAgenda->verticalScrollBar()->scroll(0, posConstentsNew.y() - posConstentsOld.y()); } } #ifndef EVENTVIEWS_NODECOS bool AgendaView::loadDecorations(const QStringList &decorations, DecorationList &decoList) { for (const QString &decoName : decorations) { if (preferences()->selectedPlugins().contains(decoName)) { CalendarDecoration::Decoration *deco = d->loadCalendarDecoration(decoName); if (deco != nullptr) { decoList << deco; } } } return !decoList.isEmpty(); } void AgendaView::placeDecorationsFrame(QFrame *frame, bool decorationsFound, bool isTop) { if (decorationsFound) { if (isTop) { // inserts in the first position d->mSplitterAgenda->insertWidget(0, frame); } else { // inserts in the last position frame->setParent(d->mSplitterAgenda); } } else { frame->setParent(this); d->mGridLayout->addWidget(frame, 0, 0); } } void AgendaView::placeDecorations(DecorationList &decoList, const QDate &date, QWidget *labelBox, bool forWeek) { for (CalendarDecoration::Decoration *deco : qAsConst(decoList)) { const CalendarDecoration::Element::List elements = forWeek ? deco->weekElements(date) : deco->dayElements(date); if (!elements.isEmpty()) { auto decoHBox = new QFrame(labelBox); labelBox->layout()->addWidget(decoHBox); auto layout = new QHBoxLayout(decoHBox); layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); decoHBox->setFrameShape(QFrame::StyledPanel); decoHBox->setMinimumWidth(1); for (CalendarDecoration::Element *it : elements) { DecorationLabel *label = new DecorationLabel(it, decoHBox); label->setAlignment(Qt::AlignBottom); label->setMinimumWidth(1); layout->addWidget(label); } } } } #endif void AgendaView::createDayLabels(bool force) { // Check if mSelectedDates has changed, if not just return // Removes some flickering and gains speed (since this is called by each updateView()) if (!force && d->mSaveSelectedDates == d->mSelectedDates) { return; } d->mSaveSelectedDates = d->mSelectedDates; delete d->mTopDayLabels; delete d->mBottomDayLabels; d->mDateDayLabels.clear(); QFontMetrics fm = fontMetrics(); d->mTopDayLabels = new QFrame(d->mTopDayLabelsFrame); d->mTopDayLabelsFrame->layout()->addWidget(d->mTopDayLabels); static_cast<QBoxLayout *>(d->mTopDayLabelsFrame->layout())->setStretchFactor(d->mTopDayLabels, 1); d->mLayoutTopDayLabels = new QHBoxLayout(d->mTopDayLabels); d->mLayoutTopDayLabels->setContentsMargins(0, 0, 0, 0); d->mLayoutTopDayLabels->setSpacing(1); // this spacer moves the day labels over to line up with the day columns QSpacerItem *spacer = new QSpacerItem((!d->mIsSideBySide ? d->mTimeLabelsZone->width() : 0) +SPACING +d->mAllDayAgenda->scrollArea()->frameWidth(), 1, QSizePolicy::Fixed); d->mLayoutTopDayLabels->addSpacerItem(spacer); auto topWeekLabelBox = new QFrame(d->mTopDayLabels); auto topWeekLabelBoxLayout = new QVBoxLayout(topWeekLabelBox); topWeekLabelBoxLayout->setContentsMargins(0, 0, 0, 0); topWeekLabelBoxLayout->setSpacing(0); d->mLayoutTopDayLabels->addWidget(topWeekLabelBox); if (d->mIsSideBySide) { topWeekLabelBox->hide(); } d->mBottomDayLabels = new QFrame(d->mBottomDayLabelsFrame); d->mBottomDayLabelsFrame->layout()->addWidget(d->mBottomDayLabels); static_cast<QBoxLayout *>(d->mBottomDayLabelsFrame->layout())->setStretchFactor( d->mBottomDayLabels, 1); d->mLayoutBottomDayLabels = new QHBoxLayout(d->mBottomDayLabels); d->mLayoutBottomDayLabels->setContentsMargins(0, 0, 0, 0); auto bottomWeekLabelBox = new QFrame(d->mBottomDayLabels); auto bottomWeekLabelBoxLayout = new QVBoxLayout(bottomWeekLabelBox); bottomWeekLabelBoxLayout->setContentsMargins(0, 0, 0, 0); bottomWeekLabelBoxLayout->setSpacing(0); d->mLayoutBottomDayLabels->addWidget(bottomWeekLabelBox); #ifndef EVENTVIEWS_NODECOS QList<CalendarDecoration::Decoration *> topDecos; QStringList topStrDecos = preferences()->decorationsAtAgendaViewTop(); placeDecorationsFrame(d->mTopDayLabelsFrame, loadDecorations(topStrDecos, topDecos), true); QList<CalendarDecoration::Decoration *> botDecos; QStringList botStrDecos = preferences()->decorationsAtAgendaViewBottom(); placeDecorationsFrame(d->mBottomDayLabelsFrame, loadDecorations(botStrDecos, botDecos), false); #endif for (const QDate &date : qAsConst(d->mSelectedDates)) { auto topDayLabelBox = new QFrame(d->mTopDayLabels); auto topDayLabelBoxLayout = new QVBoxLayout(topDayLabelBox); topDayLabelBoxLayout->setContentsMargins(0, 0, 0, 0); topDayLabelBoxLayout->setSpacing(0); d->mLayoutTopDayLabels->addWidget(topDayLabelBox); auto bottomDayLabelBox = new QFrame(d->mBottomDayLabels); auto bottomDayLabelBoxLayout = new QVBoxLayout(bottomDayLabelBox); bottomDayLabelBoxLayout->setContentsMargins(0, 0, 0, 0); bottomDayLabelBoxLayout->setSpacing(0); d->mLayoutBottomDayLabels->addWidget(bottomDayLabelBox); int dW = date.dayOfWeek(); QString veryLongStr = QLocale::system().toString(date, QLocale::LongFormat); QString longstr = i18nc("short_weekday short_monthname date (e.g. Mon Aug 13)", "%1 %2 %3", QLocale::system().dayName(dW, QLocale::ShortFormat), QLocale::system().monthName(date.month(), QLocale::ShortFormat), date.day()); QString shortstr = QString::number(date.day()); AlternateLabel *dayLabel = new AlternateLabel(shortstr, longstr, veryLongStr, topDayLabelBox); topDayLabelBoxLayout->addWidget(dayLabel); dayLabel->useShortText(); // will be recalculated in updateDayLabelSizes() anyway dayLabel->setAlignment(Qt::AlignHCenter); if (date == QDate::currentDate()) { QFont font = dayLabel->font(); font.setBold(true); dayLabel->setFont(font); } d->mDateDayLabels.append(dayLabel); // if a holiday region is selected, show the holiday name const QStringList texts = CalendarSupport::holiday(date); for (const QString &text : texts) { // Compute a small version of the holiday string for AlternateLabel const KWordWrap ww = KWordWrap::formatText(fm, topDayLabelBox->rect(), 0, text, -1); AlternateLabel *label = new AlternateLabel( ww.truncatedString(), text, text, topDayLabelBox); topDayLabelBoxLayout->addWidget(label); label->setAlignment(Qt::AlignCenter); } #ifndef EVENTVIEWS_NODECOS // Day decoration labels placeDecorations(topDecos, date, topDayLabelBox, false); placeDecorations(botDecos, date, bottomDayLabelBox, false); #endif } QSpacerItem *rightSpacer = new QSpacerItem(d->mAllDayAgenda->scrollArea()->frameWidth(), 1, QSizePolicy::Fixed); d->mLayoutTopDayLabels->addSpacerItem(rightSpacer); #ifndef EVENTVIEWS_NODECOS // Week decoration labels placeDecorations(topDecos, d->mSelectedDates.first(), topWeekLabelBox, true); placeDecorations(botDecos, d->mSelectedDates.first(), bottomWeekLabelBox, true); #endif if (!d->mIsSideBySide) { d->mLayoutTopDayLabels->addSpacing(d->mAgenda->verticalScrollBar()->width()); d->mLayoutBottomDayLabels->addSpacing(d->mAgenda->verticalScrollBar()->width()); } d->mTopDayLabels->show(); d->mBottomDayLabels->show(); // Update the labels now and after a single event loop run. Now to avoid flicker, and // delayed so that the delayed layouting size is taken into account. updateDayLabelSizes(); qDeleteAll(topDecos); qDeleteAll(botDecos); } void AgendaView::updateDayLabelSizes() { // First, calculate the maximum text type that fits for all labels AlternateLabel::TextType overallType = AlternateLabel::Extensive; for (AlternateLabel *label : qAsConst(d->mDateDayLabels)) { AlternateLabel::TextType type = label->largestFittingTextType(); if (type < overallType) { overallType = type; } } // Then, set that maximum text type to all the labels for (AlternateLabel *label : qAsConst(d->mDateDayLabels)) { label->setFixedType(overallType); } } void AgendaView::resizeEvent(QResizeEvent *resizeEvent) { updateDayLabelSizes(); EventView::resizeEvent(resizeEvent); } void AgendaView::enableAgendaUpdate(bool enable) { d->mAllowAgendaUpdate = enable; } int AgendaView::currentDateCount() const { return d->mSelectedDates.count(); } Akonadi::Item::List AgendaView::selectedIncidences() const { Akonadi::Item::List selected; KCalendarCore::Incidence::Ptr agendaitem = d->mAgenda->selectedIncidence(); if (agendaitem) { selected.append(d->mViewCalendar->item(agendaitem)); } KCalendarCore::Incidence::Ptr dayitem = d->mAllDayAgenda->selectedIncidence(); if (dayitem) { selected.append(d->mViewCalendar->item(dayitem)); } return selected; } KCalendarCore::DateList AgendaView::selectedIncidenceDates() const { KCalendarCore::DateList selected; QDate qd; qd = d->mAgenda->selectedIncidenceDate(); if (qd.isValid()) { selected.append(qd); } qd = d->mAllDayAgenda->selectedIncidenceDate(); if (qd.isValid()) { selected.append(qd); } return selected; } bool AgendaView::eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const { if (selectionStart().isValid()) { QDateTime start = selectionStart(); QDateTime end = selectionEnd(); if (start.secsTo(end) == 15 * 60) { // One cell in the agenda view selected, e.g. // because of a double-click, => Use the default duration QTime defaultDuration(CalendarSupport::KCalPrefs::instance()->defaultDuration().time()); int addSecs = (defaultDuration.hour() * 3600) + (defaultDuration.minute() * 60); end = start.addSecs(addSecs); } startDt = start; endDt = end; allDay = selectedIsAllDay(); return true; } return false; } /** returns if only a single cell is selected, or a range of cells */ bool AgendaView::selectedIsSingleCell() const { if (!selectionStart().isValid() || !selectionEnd().isValid()) { return false; } if (selectedIsAllDay()) { int days = selectionStart().daysTo(selectionEnd()); return days < 1; } else { int secs = selectionStart().secsTo(selectionEnd()); return secs <= 24 * 60 * 60 / d->mAgenda->rows(); } } void AgendaView::updateView() { fillAgenda(); } /* Update configuration settings for the agenda view. This method is not complete. */ void AgendaView::updateConfig() { // Agenda can be null if setPreferences() is called inside the ctor // We don't need to update anything in this case. if (d->mAgenda && d->mAllDayAgenda) { d->mAgenda->updateConfig(); d->mAllDayAgenda->updateConfig(); d->mTimeLabelsZone->setPreferences(preferences()); d->mTimeLabelsZone->updateAll(); updateTimeBarWidth(); setHolidayMasks(); createDayLabels(true); setChanges(changes() | ConfigChanged); updateView(); } } void AgendaView::createTimeBarHeaders() { qDeleteAll(d->mTimeBarHeaders); d->mTimeBarHeaders.clear(); const QFont oldFont(font()); QFont labelFont = d->mTimeLabelsZone->preferences()->agendaTimeLabelsFont(); labelFont.setPointSize(labelFont.pointSize() - SHRINKDOWN); const auto lst = d->mTimeLabelsZone->timeLabels(); for (QScrollArea *area : lst) { TimeLabels *timeLabel = static_cast<TimeLabels *>(area->widget()); QLabel *label = new QLabel(timeLabel->header().replace(QLatin1Char('/'), QStringLiteral("/ ")), d->mTimeBarHeaderFrame); d->mTimeBarHeaderFrame->layout()->addWidget(label); label->setFont(labelFont); label->setAlignment(Qt::AlignBottom | Qt::AlignRight); label->setContentsMargins(0, 0, 0, 0); label->setWordWrap(true); label->setToolTip(timeLabel->headerToolTip()); d->mTimeBarHeaders.append(label); } setFont(oldFont); } void AgendaView::updateTimeBarWidth() { if (d->mIsSideBySide) { return; } createTimeBarHeaders(); const QFont oldFont(font()); QFont labelFont = d->mTimeLabelsZone->preferences()->agendaTimeLabelsFont(); labelFont.setPointSize(labelFont.pointSize() - SHRINKDOWN); QFontMetrics fm(labelFont); int width = d->mTimeLabelsZone->preferedTimeLabelsWidth(); for (QLabel *l : qAsConst(d->mTimeBarHeaders)) { const auto lst = l->text().split(QLatin1Char(' ')); for (const QString &word : lst) { width = qMax(width, fm.boundingRect(word).width()); } } setFont(oldFont); width = width + fm.boundingRect(QLatin1Char('/')).width(); const int timeBarWidth = width * d->mTimeBarHeaders.count(); d->mTimeBarHeaderFrame->setFixedWidth(timeBarWidth - SPACING); d->mTimeLabelsZone->setFixedWidth(timeBarWidth); if (d->mDummyAllDayLeft) { d->mDummyAllDayLeft->setFixedWidth(0); } } void AgendaView::updateEventDates(AgendaItem *item, bool addIncidence, Akonadi::Collection::Id collectionId) { qCDebug(CALENDARVIEW_LOG) << item->text() << "; item->cellXLeft(): " << item->cellXLeft() << "; item->cellYTop(): " << item->cellYTop() << "; item->lastMultiItem(): " << item->lastMultiItem() << "; item->itemPos(): " << item->itemPos() << "; item->itemCount(): " << item->itemCount(); QDateTime startDt, endDt; // Start date of this incidence, calculate the offset from it // (so recurring and non-recurring items can be treated exactly the same, // we never need to check for recurs(), because we only move the start day // by the number of days the agenda item was really moved. Smart, isn't it?) QDate thisDate; if (item->cellXLeft() < 0) { thisDate = (d->mSelectedDates.first()).addDays(item->cellXLeft()); } else { thisDate = d->mSelectedDates[ item->cellXLeft() ]; } int daysOffset = 0; // daysOffset should only be calculated if item->cellXLeft() is positive which doesn't happen // if the event's start isn't visible. if (item->cellXLeft() >= 0) { daysOffset = item->occurrenceDate().daysTo(thisDate); } int daysLength = 0; KCalendarCore::Incidence::Ptr incidence = item->incidence(); Akonadi::Item aitem = d->mViewCalendar->item(incidence); if ((!aitem.isValid() && !addIncidence) || !incidence || !changer()) { qCWarning(CALENDARVIEW_LOG) << "changer is " << changer() << " and incidence is " << incidence.data(); return; } QTime startTime(0, 0, 0), endTime(0, 0, 0); if (incidence->allDay()) { daysLength = item->cellWidth() - 1; } else { startTime = d->mAgenda->gyToTime(item->cellYTop()); if (item->lastMultiItem()) { endTime = d->mAgenda->gyToTime(item->lastMultiItem()->cellYBottom() + 1); daysLength = item->lastMultiItem()->cellXLeft() - item->cellXLeft(); } else if (item->itemPos() == item->itemCount() && item->itemCount() > 1) { /* multiitem handling in agenda assumes two things: - The start (first KOAgendaItem) is always visible. - The first KOAgendaItem of the incidence has a non-null item->lastMultiItem() pointing to the last KOagendaItem. But those aren't always met, for example when in day-view. kolab/issue4417 */ // Cornercase 1: - Resizing the end of the event but the start isn't visible endTime = d->mAgenda->gyToTime(item->cellYBottom() + 1); daysLength = item->itemCount() - 1; startTime = incidence->dtStart().time(); } else if (item->itemPos() == 1 && item->itemCount() > 1) { // Cornercase 2: - Resizing the start of the event but the end isn't visible endTime = incidence->dateTime(KCalendarCore::Incidence::RoleEnd).time(); daysLength = item->itemCount() - 1; } else { endTime = d->mAgenda->gyToTime(item->cellYBottom() + 1); } } // FIXME: use a visitor here if (const KCalendarCore::Event::Ptr ev = CalendarSupport::event(incidence)) { startDt = incidence->dtStart(); // convert to calendar timespec because we then manipulate it // with time coming from the calendar startDt = startDt.toLocalTime(); startDt = startDt.addDays(daysOffset); if (!incidence->allDay()) { startDt.setTime(startTime); } endDt = startDt.addDays(daysLength); if (!incidence->allDay()) { endDt.setTime(endTime); } if (incidence->dtStart().toLocalTime() == startDt && ev->dtEnd().toLocalTime() == endDt) { // No change QTimer::singleShot(0, this, &AgendaView::updateView); return; } /* setDtEnd() must be called before setDtStart(), otherwise, when moving * events, CalendarLocal::incidenceUpdated() will not remove the old hash * and that causes the event to be shown in the old date also (bug #179157). * * TODO: We need a better hashing mechanism for CalendarLocal. */ ev->setDtEnd( endDt.toTimeSpec(incidence->dateTime(KCalendarCore::Incidence::RoleEnd).timeSpec())); incidence->setDtStart(startDt.toTimeSpec(incidence->dtStart().timeSpec())); } else if (const KCalendarCore::Todo::Ptr td = CalendarSupport::todo(incidence)) { endDt = td->dtDue(true).toLocalTime().addDays(daysOffset); endDt.setTime(td->allDay() ? QTime(00, 00, 00) : endTime); if (td->dtDue(true).toLocalTime() == endDt) { // No change QMetaObject::invokeMethod(this, &AgendaView::updateView, Qt::QueuedConnection); return; } const auto shift = td->dtDue(true).secsTo(endDt); startDt = td->dtStart(true).addSecs(shift); if (td->hasStartDate()) { td->setDtStart(startDt.toTimeSpec(incidence->dtStart().timeSpec())); } if (td->recurs()) { td->setDtRecurrence(td->dtRecurrence().addSecs(shift)); } td->setDtDue(endDt.toTimeSpec(td->dtDue().timeSpec()), true); } if (!incidence->hasRecurrenceId()) { item->setOccurrenceDateTime(startDt); } bool result; if (addIncidence) { Akonadi::Collection collection = calendar()->collection(collectionId); result = changer()->createIncidence(incidence, collection, this) != -1; } else { KCalendarCore::Incidence::Ptr oldIncidence(CalendarSupport::incidence(aitem)); aitem.setPayload<KCalendarCore::Incidence::Ptr>(incidence); result = changer()->modifyIncidence(aitem, oldIncidence, this) != -1; } // Update the view correctly if an agenda item move was aborted by // cancelling one of the subsequent dialogs. if (!result) { setChanges(changes() | IncidencesEdited); QMetaObject::invokeMethod(this, &AgendaView::updateView, Qt::QueuedConnection); return; } // don't update the agenda as the item already has the correct coordinates. // an update would delete the current item and recreate it, but we are still // using a pointer to that item! => CRASH enableAgendaUpdate(false); // We need to do this in a timer to make sure we are not deleting the item // we are currently working on, which would lead to crashes // Only the actually moved agenda item is already at the correct position and mustn't be // recreated. All others have to!!! if (incidence->recurs() || incidence->hasRecurrenceId()) { d->mUpdateItem = aitem; QMetaObject::invokeMethod(this, &AgendaView::updateView, Qt::QueuedConnection); } enableAgendaUpdate(true); } QDate AgendaView::startDate() const { if (d->mSelectedDates.isEmpty()) { return QDate(); } return d->mSelectedDates.first(); } QDate AgendaView::endDate() const { if (d->mSelectedDates.isEmpty()) { return QDate(); } return d->mSelectedDates.last(); } void AgendaView::showDates(const QDate &start, const QDate &end, const QDate &preferredMonth) { Q_UNUSED(preferredMonth); if (!d->mSelectedDates.isEmpty() && d->mSelectedDates.first() == start && d->mSelectedDates.last() == end) { return; } if (!start.isValid() || !end.isValid() || start > end || start.daysTo(end) > MAX_DAY_COUNT) { qCWarning(CALENDARVIEW_LOG) << "got bizare parameters: " << start << end << " - aborting here"; return; } d->mSelectedDates = d->generateDateList(start, end); // and update the view setChanges(changes() | DatesChanged); fillAgenda(); d->mTimeLabelsZone->update(); } void AgendaView::showIncidences(const Akonadi::Item::List &incidences, const QDate &date) { Q_UNUSED(date); if (!calendar()) { qCCritical(CALENDARVIEW_LOG) << "No Calendar set"; return; } // we must check if they are not filtered; if they are, remove the filter KCalendarCore::CalFilter *filter = calendar()->filter(); bool wehaveall = true; if (filter) { for (const Akonadi::Item &aitem : incidences) { if (!(wehaveall = filter->filterIncidence(CalendarSupport::incidence(aitem)))) { break; } } } if (!wehaveall) { calendar()->setFilter(nullptr); } QDateTime start = CalendarSupport::incidence(incidences.first())->dtStart().toLocalTime(); QDateTime end = CalendarSupport::incidence(incidences.first())->dateTime( KCalendarCore::Incidence::RoleEnd).toLocalTime(); Akonadi::Item first = incidences.first(); for (const Akonadi::Item &aitem : incidences) { if (CalendarSupport::incidence(aitem)->dtStart().toLocalTime() < start) { first = aitem; } start = qMin(start, CalendarSupport::incidence(aitem)->dtStart().toLocalTime()); end = qMax(start, CalendarSupport::incidence(aitem)->dateTime( KCalendarCore::Incidence::RoleEnd).toLocalTime()); } end.toTimeZone(start.timeZone()); // allow direct comparison of dates if (start.date().daysTo(end.date()) + 1 <= currentDateCount()) { showDates(start.date(), end.date()); } else { showDates(start.date(), start.date().addDays(currentDateCount() - 1)); } d->mAgenda->selectItem(first); } void AgendaView::fillAgenda() { if (changes() == NothingChanged) { return; } if (d->mViewCalendar->calendars() == 0) { qCWarning(CALENDARVIEW_LOG) << "No calendar is set"; return; } /* qCDebug(CALENDARVIEW_LOG) << "changes = " << changes() << "; mUpdateAgenda = " << d->mUpdateAgenda << "; mUpdateAllDayAgenda = " << d->mUpdateAllDayAgenda; */ /* Remember the item Ids of the selected items. In case one of the * items was deleted and re-added, we want to reselect it. */ const QString selectedAgendaId = d->mAgenda->lastSelectedItemUid(); const QString selectedAllDayAgendaId = d->mAllDayAgenda->lastSelectedItemUid(); enableAgendaUpdate(true); d->clearView(); if (changes().testFlag(DatesChanged)) { d->mAllDayAgenda->changeColumns(d->mSelectedDates.count()); d->mAgenda->changeColumns(d->mSelectedDates.count()); d->changeColumns(d->mSelectedDates.count()); createDayLabels(false); setHolidayMasks(); d->mAgenda->setDateList(d->mSelectedDates); } setChanges(NothingChanged); bool somethingReselected = false; const KCalendarCore::Incidence::List incidences = d->mViewCalendar->incidences(); for (const KCalendarCore::Incidence::Ptr &incidence : incidences) { Q_ASSERT(incidence); const bool wasSelected = (incidence->uid() == selectedAgendaId) || (incidence->uid() == selectedAllDayAgendaId); if ((incidence->allDay() && d->mUpdateAllDayAgenda) || (!incidence->allDay() && d->mUpdateAgenda)) { displayIncidence(incidence, wasSelected); } if (wasSelected) { somethingReselected = true; } } d->mAgenda->checkScrollBoundaries(); updateEventIndicators(); // mAgenda->viewport()->update(); // mAllDayAgenda->viewport()->update(); // make invalid deleteSelectedDateTime(); d->mUpdateAgenda = false; d->mUpdateAllDayAgenda = false; if (!somethingReselected) { Q_EMIT incidenceSelected(Akonadi::Item(), QDate()); } } bool AgendaView::displayIncidence(const KCalendarCore::Incidence::Ptr &incidence, bool createSelected) { if (!incidence || incidence->hasRecurrenceId()) { return false; } KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(incidence); if (todo && (!preferences()->showTodosAgendaView() || !todo->hasDueDate())) { return false; } KCalendarCore::Event::Ptr event = CalendarSupport::event(incidence); const QDate today = QDate::currentDate(); QDateTime firstVisibleDateTime(d->mSelectedDates.first(), QTime(0, 0, 0), Qt::LocalTime); QDateTime lastVisibleDateTime(d->mSelectedDates.last(), QTime(23, 59, 59, 999), Qt::LocalTime); // Optimization, very cheap operation that discards incidences that aren't in the timespan if (!d->mightBeVisible(incidence)) { return false; } std::vector<QDateTime> dateTimeList; const QDateTime incDtStart = incidence->dtStart().toLocalTime(); const QDateTime incDtEnd = incidence->dateTime(KCalendarCore::Incidence::RoleEnd).toLocalTime(); bool alreadyAddedToday = false; if (incidence->recurs()) { // timed incidences occur in [dtStart(), dtEnd()[ // all-day incidences occur in [dtStart(), dtEnd()] // so we subtract 1 second in the timed case const int secsToAdd = incidence->allDay() ? 0 : -1; const int eventDuration = event ? incDtStart.daysTo(incDtEnd.addSecs(secsToAdd)) : 0; // if there's a multiday event that starts before firstVisibleDateTime but ends after // lets include it. timesInInterval() ignores incidences that aren't totaly inside // the range const QDateTime startDateTimeWithOffset = firstVisibleDateTime.addDays(-eventDuration); KCalendarCore::OccurrenceIterator rIt(*calendar(), incidence, startDateTimeWithOffset, lastVisibleDateTime); while (rIt.hasNext()) { rIt.next(); auto occurrenceDate = rIt.occurrenceStartDate().toLocalTime(); if (const auto todo = CalendarSupport::todo(rIt.incidence())) { // Recurrence exceptions may have durations different from the normal recurrences. occurrenceDate = occurrenceDate.addSecs(todo->dtStart().secsTo(todo->dtDue())); } const bool makesDayBusy = preferences()->colorAgendaBusyDays() && makesWholeDayBusy(rIt.incidence()); if (makesDayBusy) { KCalendarCore::Event::List &busyEvents = d->mBusyDays[occurrenceDate.date()]; busyEvents.append(event); } if (occurrenceDate.date() == today) { alreadyAddedToday = true; } d->insertIncidence(rIt.incidence(), rIt.recurrenceId(), occurrenceDate, createSelected); } } else { QDateTime dateToAdd; // date to add to our date list QDateTime incidenceEnd; if (todo && todo->hasDueDate() && !todo->isOverdue()) { // If it's not overdue it will be shown at the original date (not today) dateToAdd = todo->dtDue().toLocalTime(); // To-dos due at a specific time are drawn with the bottom of the rectangle at dtDue. // If dtDue is at 00:00, then it should be displayed in the previous day, at 23:59. if (!todo->allDay() && dateToAdd.time() == QTime(0, 0)) { dateToAdd = dateToAdd.addSecs(-1); } incidenceEnd = dateToAdd; } else if (event) { dateToAdd = incDtStart; incidenceEnd = incDtEnd; } if (dateToAdd.isValid() && incidence->allDay()) { // so comparisons with < > actually work dateToAdd.setTime(QTime(0, 0)); incidenceEnd.setTime(QTime(23, 59, 59, 999)); } if (dateToAdd <= lastVisibleDateTime && incidenceEnd > firstVisibleDateTime) { dateTimeList.push_back(dateToAdd); } } // ToDo items shall be displayed today if they are overdue const QDateTime dateTimeToday = QDateTime(today, QTime(0, 0), Qt::LocalTime); if (todo && todo->isOverdue() && dateTimeToday >= firstVisibleDateTime && dateTimeToday <= lastVisibleDateTime) { /* If there's a recurring instance showing up today don't add "today" again * we don't want the event to appear duplicated */ if (!alreadyAddedToday) { dateTimeList.push_back(dateTimeToday); } } const bool makesDayBusy = preferences()->colorAgendaBusyDays() && makesWholeDayBusy(incidence); for (auto t = dateTimeList.begin(); t != dateTimeList.end(); ++t) { if (makesDayBusy) { KCalendarCore::Event::List &busyEvents = d->mBusyDays[(*t).date()]; busyEvents.append(event); } d->insertIncidence(incidence, t->toLocalTime(), t->toLocalTime(), createSelected); } // Can be multiday if (event && makesDayBusy && event->isMultiDay()) { const QDate lastVisibleDate = d->mSelectedDates.last(); for (QDate date = event->dtStart().date(); date <= event->dtEnd().date() && date <= lastVisibleDate; date = date.addDays(1)) { KCalendarCore::Event::List &busyEvents = d->mBusyDays[date]; busyEvents.append(event); } } return !dateTimeList.empty(); } void AgendaView::updateEventIndicatorTop(int newY) { for (int i = 0; i < d->mMinY.size(); ++i) { d->mEventIndicatorTop->enableColumn(i, newY > d->mMinY[i]); } d->mEventIndicatorTop->update(); } void AgendaView::updateEventIndicatorBottom(int newY) { for (int i = 0; i < d->mMaxY.size(); ++i) { d->mEventIndicatorBottom->enableColumn(i, newY <= d->mMaxY[i]); } d->mEventIndicatorBottom->update(); } void AgendaView::slotIncidencesDropped(const QList<QUrl> &items, const QPoint &gpos, bool allDay) { Q_UNUSED(items); Q_UNUSED(gpos); Q_UNUSED(allDay); #ifdef AKONADI_PORT_DISABLED // one item -> multiple items, Incidence* -> akonadi item url // (we might have to fetch the items here first!) if (gpos.x() < 0 || gpos.y() < 0) { return; } const QDate day = d->mSelectedDates[gpos.x()]; const QTime time = d->mAgenda->gyToTime(gpos.y()); KDateTime newTime(day, time, preferences()->timeSpec()); newTime.setDateOnly(allDay); Todo::Ptr todo = CalendarSupport::todo(todoItem); if (todo && dynamic_cast<Akonadi::ETMCalendar *>(calendar())) { const Akonadi::Item existingTodoItem = calendar()->itemForIncidence(calendar()->todo(todo->uid())); if (Todo::Ptr existingTodo = CalendarSupport::todo(existingTodoItem)) { qCDebug(CALENDARVIEW_LOG) << "Drop existing Todo"; Todo::Ptr oldTodo(existingTodo->clone()); if (changer()) { existingTodo->setDtDue(newTime); existingTodo->setAllDay(allDay); changer()->modifyIncidence(existingTodoItem, oldTodo, this); } else { KMessageBox::sorry(this, i18n("Unable to modify this to-do, " "because it cannot be locked.")); } } else { qCDebug(CALENDARVIEW_LOG) << "Drop new Todo"; todo->setDtDue(newTime); todo->setAllDay(allDay); if (!changer()->addIncidence(todo, this)) { KMessageBox::sorry(this, i18n("Unable to save %1 \"%2\".", i18n(todo->type()), todo->summary())); } } } #else qCDebug(CALENDARVIEW_LOG) << "AKONADI PORT: Disabled code in " << Q_FUNC_INFO; #endif } static void setDateTime(KCalendarCore::Incidence::Ptr incidence, QDateTime dt, bool allDay) { incidence->setAllDay(allDay); if (auto todo = CalendarSupport::todo(incidence)) { // To-dos are displayed on their due date and time. Make sure the todo is displayed // where it was dropped. QDateTime dtStart = todo->dtStart(); if (dtStart.isValid()) { auto duration = todo->dtStart().daysTo(todo->dtDue()); dtStart = dt.addDays(-duration); dtStart.setTime({0, 0, 0}); } // Set dtDue before dtStart; see comment in updateEventDates(). todo->setDtDue(dt, true); todo->setDtStart(dtStart); } else if (auto event = CalendarSupport::event(incidence)) { auto duration = event->dtStart().secsTo(event->dtEnd()); if (duration == 0) { auto defaultDuration = CalendarSupport::KCalPrefs::instance()->defaultDuration().time(); duration = (defaultDuration.hour() * 3600) + (defaultDuration.minute() * 60); } event->setDtEnd(dt.addSecs(duration)); event->setDtStart(dt); } else { // Can't happen, but ... incidence->setDtStart(dt); } } void AgendaView::slotIncidencesDropped(const KCalendarCore::Incidence::List &incidences, const QPoint &gpos, bool allDay) { if (gpos.x() < 0 || gpos.y() < 0) { return; } const QDate day = d->mSelectedDates[gpos.x()]; const QTime time = d->mAgenda->gyToTime(gpos.y()); QDateTime newTime(day, time, Qt::LocalTime); for (const KCalendarCore::Incidence::Ptr &incidence : incidences) { const Akonadi::Item existingItem = calendar()->item(incidence); const bool existsInSameCollection = existingItem.isValid() && (existingItem.storageCollectionId() == collectionId() || collectionId() == -1); if (existingItem.isValid() && existsInSameCollection) { KCalendarCore::Incidence::Ptr newIncidence = existingItem.payload<KCalendarCore::Incidence::Ptr>(); if (newIncidence->dtStart() == newTime && newIncidence->allDay() == allDay) { // Nothing changed continue; } KCalendarCore::Incidence::Ptr oldIncidence(newIncidence->clone()); setDateTime(newIncidence, newTime, allDay); (void) changer()->modifyIncidence(existingItem, oldIncidence, this); } else { // Create a new one // The drop came from another application. Create a new incidence. setDateTime(incidence, newTime, allDay); incidence->setUid(KCalendarCore::CalFormat::createUniqueId()); Akonadi::Collection collection(collectionId()); const bool added = -1 != changer()->createIncidence(incidence, collection, this); if (added) { // TODO: make async if (existingItem.isValid()) { // Dragged from one agenda to another, delete origin (void) changer()->deleteIncidence(existingItem); } } } } } void AgendaView::startDrag(const KCalendarCore::Incidence::Ptr &incidence) { if (!calendar()) { qCCritical(CALENDARVIEW_LOG) << "No Calendar set"; return; } const Akonadi::Item item = d->mViewCalendar->item(incidence); if (item.isValid()) { startDrag(item); } } void AgendaView::startDrag(const Akonadi::Item &incidence) { if (!calendar()) { qCCritical(CALENDARVIEW_LOG) << "No Calendar set"; return; } if (QDrag *drag = CalendarSupport::createDrag(incidence, this)) { drag->exec(); } } void AgendaView::readSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); readSettings(config.data()); } void AgendaView::readSettings(const KConfig *config) { const KConfigGroup group = config->group("Views"); const QList<int> sizes = group.readEntry("Separator AgendaView", QList<int>()); // the size depends on the number of plugins used // we don't want to read invalid/corrupted settings or else agenda becomes invisible if (sizes.count() >= 2 && !sizes.contains(0)) { d->mSplitterAgenda->setSizes(sizes); updateConfig(); } } void AgendaView::writeSettings(KConfig *config) { KConfigGroup group = config->group("Views"); QList<int> list = d->mSplitterAgenda->sizes(); group.writeEntry("Separator AgendaView", list); } QVector<bool> AgendaView::busyDayMask() const { if (d->mSelectedDates.isEmpty() || !d->mSelectedDates[0].isValid()) { return QVector<bool>(); } QVector<bool> busyDayMask; busyDayMask.resize(d->mSelectedDates.count()); for (int i = 0; i < d->mSelectedDates.count(); ++i) { busyDayMask[i] = !d->mBusyDays[d->mSelectedDates[i]].isEmpty(); } return busyDayMask; } void AgendaView::setHolidayMasks() { if (d->mSelectedDates.isEmpty() || !d->mSelectedDates[0].isValid()) { return; } d->mHolidayMask.resize(d->mSelectedDates.count() + 1); const QList<QDate> workDays = CalendarSupport::workDays(d->mSelectedDates.first().addDays(-1), d->mSelectedDates.last()); for (int i = 0; i < d->mSelectedDates.count(); ++i) { d->mHolidayMask[i] = !workDays.contains(d->mSelectedDates[ i ]); } // Store the information about the day before the visible area (needed for // overnight working hours) in the last bit of the mask: bool showDay = !workDays.contains(d->mSelectedDates[ 0 ].addDays(-1)); d->mHolidayMask[ d->mSelectedDates.count() ] = showDay; d->mAgenda->setHolidayMask(&d->mHolidayMask); d->mAllDayAgenda->setHolidayMask(&d->mHolidayMask); } void AgendaView::clearSelection() { d->mAgenda->deselectItem(); d->mAllDayAgenda->deselectItem(); } void AgendaView::newTimeSpanSelectedAllDay(const QPoint &start, const QPoint &end) { newTimeSpanSelected(start, end); d->mTimeSpanInAllDay = true; } void AgendaView::newTimeSpanSelected(const QPoint &start, const QPoint &end) { if (!d->mSelectedDates.count()) { return; } d->mTimeSpanInAllDay = false; const QDate dayStart = d->mSelectedDates[ qBound(0, start.x(), (int)d->mSelectedDates.size() - 1) ]; const QDate dayEnd = d->mSelectedDates[ qBound(0, end.x(), (int)d->mSelectedDates.size() - 1) ]; const QTime timeStart = d->mAgenda->gyToTime(start.y()); const QTime timeEnd = d->mAgenda->gyToTime(end.y() + 1); d->mTimeSpanBegin = QDateTime(dayStart, timeStart); d->mTimeSpanEnd = QDateTime(dayEnd, timeEnd); } QDateTime AgendaView::selectionStart() const { return d->mTimeSpanBegin; } QDateTime AgendaView::selectionEnd() const { return d->mTimeSpanEnd; } bool AgendaView::selectedIsAllDay() const { return d->mTimeSpanInAllDay; } void AgendaView::deleteSelectedDateTime() { d->mTimeSpanBegin.setDate(QDate()); d->mTimeSpanEnd.setDate(QDate()); d->mTimeSpanInAllDay = false; } void AgendaView::removeIncidence(const KCalendarCore::Incidence::Ptr &incidence) { // Don't wrap this in a if (incidence->isAllDay) because whe all day // property might have changed d->mAllDayAgenda->removeIncidence(incidence); d->mAgenda->removeIncidence(incidence); if (!incidence->hasRecurrenceId() && d->mViewCalendar->isValid(incidence->uid())) { // Deleted incidence is an main incidence // Delete all exceptions as well const KCalendarCore::Incidence::List exceptions = calendar2(incidence->uid())->instances(incidence); for (const KCalendarCore::Incidence::Ptr &exception : exceptions) { if (exception->allDay()) { d->mAllDayAgenda->removeIncidence(exception); } else { d->mAgenda->removeIncidence(exception); } } } } void AgendaView::updateEventIndicators() { d->mUpdateEventIndicatorsScheduled = false; d->mMinY = d->mAgenda->minContentsY(); d->mMaxY = d->mAgenda->maxContentsY(); d->mAgenda->checkScrollBoundaries(); updateEventIndicatorTop(d->mAgenda->visibleContentsYMin()); updateEventIndicatorBottom(d->mAgenda->visibleContentsYMax()); } void AgendaView::setIncidenceChanger(Akonadi::IncidenceChanger *changer) { EventView::setIncidenceChanger(changer); d->mAgenda->setIncidenceChanger(changer); d->mAllDayAgenda->setIncidenceChanger(changer); } void AgendaView::clearTimeSpanSelection() { d->mAgenda->clearSelection(); d->mAllDayAgenda->clearSelection(); deleteSelectedDateTime(); } Agenda *AgendaView::agenda() const { return d->mAgenda; } Agenda *AgendaView::allDayAgenda() const { return d->mAllDayAgenda; } QSplitter *AgendaView::splitter() const { return d->mSplitterAgenda; } bool AgendaView::filterByCollectionSelection(const KCalendarCore::Incidence::Ptr &incidence) { const Akonadi::Item item = d->mViewCalendar->item(incidence); if (!item.isValid()) { return true; } if (customCollectionSelection()) { return customCollectionSelection()->contains(item.parentCollection().id()); } if (collectionId() < 0) { return true; } else { return collectionId() == item.storageCollectionId(); } } void AgendaView::alignAgendas() { // resize dummy widget so the allday agenda lines up with the hourly agenda. if (d->mDummyAllDayLeft) { d->mDummyAllDayLeft->setFixedWidth(d->mTimeLabelsZone->width() -d->mTimeBarHeaderFrame->width() -SPACING); } // Must be async, so they are centered createDayLabels(true); } CalendarDecoration::Decoration *AgendaView::Private::loadCalendarDecoration(const QString &name) { const QString type = CalendarSupport::Plugin::serviceType(); const int version = CalendarSupport::Plugin::interfaceVersion(); QString constraint; if (version >= 0) { constraint = QStringLiteral("[X-KDE-PluginInterfaceVersion] == %1").arg(QString::number(version)); } KService::List list = KServiceTypeTrader::self()->query(type, constraint); KService::List::ConstIterator it; for (it = list.constBegin(); it != list.constEnd(); ++it) { if ((*it)->desktopEntryName() == name) { KService::Ptr service = *it; KPluginLoader loader(*service); auto factory = loader.instance(); if (!factory) { qCDebug(CALENDARVIEW_LOG) << "Factory creation failed"; return nullptr; } auto pluginFactory = qobject_cast<CalendarDecoration::DecorationFactory *>(factory); if (!pluginFactory) { qCDebug(CALENDARVIEW_LOG) << "Cast failed"; return nullptr; } return pluginFactory->createPluginFactory(); } } return nullptr; } void AgendaView::setChanges(EventView::Changes changes) { d->setChanges(changes); } void AgendaView::scheduleUpdateEventIndicators() { if (!d->mUpdateEventIndicatorsScheduled) { d->mUpdateEventIndicatorsScheduled = true; QTimer::singleShot(0, this, &AgendaView::updateEventIndicators); } } 07070100000010000081A40000000200000002000000015F0BF3C900002306000000000000000000000000000000000000004600000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/agendaview.h/* Copyright (c) 2000,2001,2003 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com Author: Sergio Martins, sergio.martins@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_AGENDAVIEW_H #define EVENTVIEWS_AGENDAVIEW_H #include "eventviews_export.h" #include "eventview.h" #include "viewcalendar.h" #include <KCalendarCore/Todo> #include <QFrame> class KConfig; class QSplitter; namespace EventViews { #ifndef EVENTVIEWS_NODECOS namespace CalendarDecoration { class Decoration; } #endif class TimeLabels; class TimeLabelsZone; class Agenda; class AgendaItem; class AgendaView; class EventIndicator : public QFrame { Q_OBJECT public: enum Location { Top, Bottom }; explicit EventIndicator(Location loc = Top, QWidget *parent = nullptr); ~EventIndicator() override; void changeColumns(int columns); void enableColumn(int column, bool enable); protected: void paintEvent(QPaintEvent *event) override; bool eventFilter(QObject *, QEvent *) override; private: class Private; Private *const d; }; /** AgendaView is the agenda-like view that displays events in a single or multi-day view. */ class EVENTVIEWS_EXPORT AgendaView : public EventView { Q_OBJECT public: explicit AgendaView(const PrefsPtr &preferences, const QDate &start, const QDate &end, bool isInteractive, bool isSideBySide = false, QWidget *parent = nullptr); explicit AgendaView(const QDate &start, const QDate &end, bool isInteractive, bool isSideBySide = false, QWidget *parent = nullptr); ~AgendaView() override; enum { MAX_DAY_COUNT = 42 // (6 * 7) }; /** Returns number of currently shown dates. */ Q_REQUIRED_RESULT int currentDateCount() const override; /** returns the currently selected events */ Q_REQUIRED_RESULT Akonadi::Item::List selectedIncidences() const override; /** returns the currently selected incidence's dates */ Q_REQUIRED_RESULT KCalendarCore::DateList selectedIncidenceDates() const override; /** return the default start/end date/time for new events */ bool eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const override; /** start-datetime of selection */ Q_REQUIRED_RESULT QDateTime selectionStart() const override; /** end-datetime of selection */ Q_REQUIRED_RESULT QDateTime selectionEnd() const override; /** returns true if selection is for whole day */ Q_REQUIRED_RESULT bool selectedIsAllDay() const; /** make selected start/end invalid */ void deleteSelectedDateTime(); /** returns if only a single cell is selected, or a range of cells */ Q_REQUIRED_RESULT bool selectedIsSingleCell() const; /* reimp from EventView */ void setCalendar(const Akonadi::ETMCalendar::Ptr &cal) override; virtual void addCalendar(const ViewCalendar::Ptr &cal); QSplitter *splitter() const; // FIXME: we already have startDateTime() and endDateTime() in the base class /** First shown day */ Q_REQUIRED_RESULT QDate startDate() const; /** Last shown day */ Q_REQUIRED_RESULT QDate endDate() const; /** Update event belonging to agenda item If the incidence is multi-day, item is the first one */ void updateEventDates(AgendaItem *item, bool addIncidence, Akonadi::Collection::Id collectionId); Q_REQUIRED_RESULT QVector<bool> busyDayMask() const; /** * Return calendar object for a concrete incidence. * this function is able to use multiple calenders * TODO: replace EventsView::calendar() */ virtual KCalendarCore::Calendar::Ptr calendar2(const KCalendarCore::Incidence::Ptr &incidence) const; virtual KCalendarCore::Calendar::Ptr calendar2(const QString &incidenceIdentifier) const; void showDates(const QDate &start, const QDate &end, const QDate &preferredMonth = QDate()) override; void showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) override; void clearSelection() override; void startDrag(const Akonadi::Item &); void readSettings(); void readSettings(const KConfig *); void writeSettings(KConfig *); void enableAgendaUpdate(bool enable); void setIncidenceChanger(Akonadi::IncidenceChanger *changer) override; void zoomInHorizontally(const QDate &date = QDate()); void zoomOutHorizontally(const QDate &date = QDate()); void zoomInVertically(); void zoomOutVertically(); void zoomView(const int delta, const QPoint &pos, const Qt::Orientation orient = Qt::Horizontal); void clearTimeSpanSelection(); // Used by the timelabelszone void updateTimeBarWidth(); /** Create labels for the selected dates. */ void createDayLabels(bool force); void createTimeBarHeaders(); void setChanges(EventView::Changes) override; Q_SIGNALS: void showNewEventPopupSignal(); void showIncidencePopupSignal(const Akonadi::Item &, const QDate &); void zoomViewHorizontally(const QDate &, int count); void timeSpanSelectionChanged(); protected: /** Fill agenda using the current set value for the start date */ void fillAgenda(); void connectAgenda(Agenda *agenda, Agenda *otherAgenda); /** Set the masks on the agenda widgets indicating, which days are holidays. */ void setHolidayMasks(); void removeIncidence(const KCalendarCore::Incidence::Ptr &inc); void resizeEvent(QResizeEvent *resizeEvent) override; public Q_SLOTS: void updateView() override; void updateConfig() override; /** reschedule the todo to the given x- and y- coordinates. Third parameter determines all-day (no time specified) */ void slotIncidencesDropped(const KCalendarCore::Incidence::List &incidences, const QPoint &, bool); void slotIncidencesDropped(const QList<QUrl> &incidences, const QPoint &, bool); void startDrag(const KCalendarCore::Incidence::Ptr &); protected Q_SLOTS: void updateEventIndicatorTop(int newY); void updateEventIndicatorBottom(int newY); /** Updates data for selected timespan */ void newTimeSpanSelected(const QPoint &start, const QPoint &end); /** Updates data for selected timespan for all day event*/ void newTimeSpanSelectedAllDay(const QPoint &start, const QPoint &end); /** Updates the event indicators after a certain incidence was modified or removed. */ void updateEventIndicators(); void scheduleUpdateEventIndicators(); void updateDayLabelSizes(); void alignAgendas(); private Q_SLOTS: void slotIncidenceSelected(const KCalendarCore::Incidence::Ptr &incidence, const QDate &date); void slotShowIncidencePopup(const KCalendarCore::Incidence::Ptr &incidence, const QDate &date); void slotEditIncidence(const KCalendarCore::Incidence::Ptr &incidence); void slotShowIncidence(const KCalendarCore::Incidence::Ptr &incidence); void slotDeleteIncidence(const KCalendarCore::Incidence::Ptr &incidence); private: void init(const QDate &start, const QDate &end); bool filterByCollectionSelection(const KCalendarCore::Incidence::Ptr &incidence); void setupTimeLabel(TimeLabels *timeLabel); bool displayIncidence(const KCalendarCore::Incidence::Ptr &incidence, bool createSelected); #ifndef EVENTVIEWS_NODECOS typedef QList<EventViews::CalendarDecoration::Decoration *> DecorationList; bool loadDecorations(const QStringList &decorations, DecorationList &decoList); void placeDecorationsFrame(QFrame *frame, bool decorationsFound, bool isTop); void placeDecorations(EventViews::AgendaView::DecorationList &decoList, const QDate &date, QWidget *labelBox, bool forWeek); #endif friend class TimeLabelsZone; friend class MultiAgendaView; Agenda *agenda() const; Agenda *allDayAgenda() const; private: class Private; Private *const d; }; } #endif 07070100000011000081A40000000200000002000000015F0BF3C900000F7F000000000000000000000000000000000000004C00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/alternatelabel.cpp/* Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "alternatelabel.h" using namespace EventViews; AlternateLabel::AlternateLabel(const QString &shortlabel, const QString &longlabel, const QString &extensivelabel, QWidget *parent) : QLabel(parent) , mTextTypeFixed(false) , mShortText(shortlabel) , mLongText(longlabel) , mExtensiveText(extensivelabel) { setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); if (mExtensiveText.isEmpty()) { mExtensiveText = mLongText; } const QFontMetrics &fm = fontMetrics(); // We use at least averageCharWidth * 2 here to avoid misalignment // for single char labels. setMinimumWidth(qMax(fm.averageCharWidth() * 2, fm.boundingRect(shortlabel).width()) + getIndent()); squeezeTextToLabel(); } AlternateLabel::~AlternateLabel() { } void AlternateLabel::useShortText() { mTextTypeFixed = true; QLabel::setText(mShortText); setToolTip(mExtensiveText); } void AlternateLabel::useLongText() { mTextTypeFixed = true; QLabel::setText(mLongText); this->setToolTip(mExtensiveText); } void AlternateLabel::useExtensiveText() { mTextTypeFixed = true; QLabel::setText(mExtensiveText); this->setToolTip(QString()); } void AlternateLabel::useDefaultText() { mTextTypeFixed = false; squeezeTextToLabel(); } void AlternateLabel::squeezeTextToLabel() { if (mTextTypeFixed) { return; } QFontMetrics fm(fontMetrics()); int labelWidth = size().width() - getIndent(); int textWidth = fm.boundingRect(mLongText).width(); int longTextWidth = fm.boundingRect(mExtensiveText).width(); if (longTextWidth <= labelWidth) { QLabel::setText(mExtensiveText); this->setToolTip(QString()); } else if (textWidth <= labelWidth) { QLabel::setText(mLongText); this->setToolTip(mExtensiveText); } else { QLabel::setText(mShortText); this->setToolTip(mExtensiveText); } } void AlternateLabel::resizeEvent(QResizeEvent *) { squeezeTextToLabel(); } AlternateLabel::TextType AlternateLabel::largestFittingTextType() const { QFontMetrics fm(fontMetrics()); const int labelWidth = size().width() - getIndent(); const int longTextWidth = fm.boundingRect(mLongText).width(); const int extensiveTextWidth = fm.boundingRect(mExtensiveText).width(); if (extensiveTextWidth <= labelWidth) { return Extensive; } else if (longTextWidth <= labelWidth) { return Long; } else { return Short; } } void AlternateLabel::setFixedType(TextType type) { switch (type) { case Extensive: useExtensiveText(); break; case Long: useLongText(); break; case Short: useShortText(); break; } } int AlternateLabel::getIndent() const { return indent() == -1 ? fontMetrics().boundingRect(QStringLiteral("x")).width() / 2 : indent(); } 07070100000012000081A40000000200000002000000015F0BF3C90000079F000000000000000000000000000000000000004A00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/alternatelabel.h/* Copyright (c) 2000,2001,2003 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_ALTERNATELABEL_H #define EVENTVIEWS_ALTERNATELABEL_H #include <QLabel> namespace EventViews { class AlternateLabel : public QLabel { Q_OBJECT public: AlternateLabel(const QString &shortlabel, const QString &longlabel, const QString &extensivelabel = QString(), QWidget *parent = nullptr); ~AlternateLabel() override; enum TextType { Short = 0, Long = 1, Extensive = 2 }; Q_REQUIRED_RESULT TextType largestFittingTextType() const; void setFixedType(TextType type); public Q_SLOTS: void useShortText(); void useLongText(); void useExtensiveText(); void useDefaultText(); protected: void resizeEvent(QResizeEvent *) override; virtual void squeezeTextToLabel(); bool mTextTypeFixed = false; QString mShortText, mLongText, mExtensiveText; private: int getIndent() const; }; } #endif 07070100000013000081A40000000200000002000000015F0BF3C900001816000000000000000000000000000000000000005000000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/calendardecoration.cpp/* Copyright (c) 2007 Loïc Corbasson <loic.corbasson@gmail.com> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "calendardecoration.h" #include <QDate> using namespace EventViews::CalendarDecoration; Element::Element(const QString &id) : mId(id) { } Element::~Element() { } QString Element::id() const { return mId; } QString Element::elementInfo() const { return QString(); } QString Element::shortText() { return QString(); } QString Element::longText() { return QString(); } QString Element::extensiveText() { return QString(); } QPixmap Element::newPixmap(const QSize &) { return QPixmap(); } QUrl Element::url() { return QUrl(); } //////////////////////////////////////////////////////////////////////////////// StoredElement::StoredElement(const QString &id) : Element(id) { } StoredElement::StoredElement(const QString &id, const QString &shortText) : Element(id) , mShortText(shortText) { } StoredElement::StoredElement(const QString &id, const QString &shortText, const QString &longText) : Element(id) , mShortText(shortText) , mLongText(longText) { } StoredElement::StoredElement(const QString &id, const QString &shortText, const QString &longText, const QString &extensiveText) : Element(id) , mShortText(shortText) , mLongText(longText) , mExtensiveText(extensiveText) { } StoredElement::StoredElement(const QString &id, const QPixmap &pixmap) : Element(id) , mPixmap(pixmap) { } void StoredElement::setShortText(const QString &t) { mShortText = t; } QString StoredElement::shortText() { return mShortText; } void StoredElement::setLongText(const QString &t) { mLongText = t; } QString StoredElement::longText() { return mLongText; } void StoredElement::setExtensiveText(const QString &t) { mExtensiveText = t; } QString StoredElement::extensiveText() { return mExtensiveText; } void StoredElement::setPixmap(const QPixmap &p) { mPixmap = p; } QPixmap StoredElement::pixmap() { return mPixmap; } void StoredElement::setUrl(const QUrl &u) { mUrl = u; } QUrl StoredElement::url() { return mUrl; } //////////////////////////////////////////////////////////////////////////////// Decoration::Decoration() { } Decoration::~Decoration() { //Deleted by label directly. #if 0 for (Element::List lst : qAsConst(mDayElements)) { qDeleteAll(lst); lst.clear(); } for (Element::List lst : qAsConst(mWeekElements)) { qDeleteAll(lst); lst.clear(); } for (Element::List lst : qAsConst(mMonthElements)) { qDeleteAll(lst); lst.clear(); } for (Element::List lst : qAsConst(mYearElements)) { qDeleteAll(lst); lst.clear(); } #endif mDayElements.clear(); mWeekElements.clear(); mMonthElements.clear(); mYearElements.clear(); } Element::List Decoration::dayElements(const QDate &date) { QMap<QDate, Element::List>::ConstIterator it; it = mDayElements.constFind(date); if (it == mDayElements.constEnd()) { return registerDayElements(createDayElements(date), date); } else { return *it; } } Element::List Decoration::weekElements(const QDate &d) { QDate date = weekDate(d); QMap<QDate, Element::List>::ConstIterator it; it = mWeekElements.constFind(date); if (it == mWeekElements.constEnd()) { return registerWeekElements(createWeekElements(date), date); } else { return *it; } } Element::List Decoration::monthElements(const QDate &d) { QDate date = monthDate(d); QMap<QDate, Element::List>::ConstIterator it; it = mMonthElements.constFind(date); if (it == mMonthElements.constEnd()) { return registerMonthElements(createMonthElements(date), date); } else { return *it; } } Element::List Decoration::yearElements(const QDate &d) { QDate date = yearDate(d); QMap<QDate, Element::List>::ConstIterator it; it = mYearElements.constFind(date); if (it == mYearElements.constEnd()) { return registerYearElements(createYearElements(date), date); } else { return *it; } } Element::List Decoration::registerDayElements(const Element::List &e, const QDate &d) { mDayElements.insert(d, e); return e; } Element::List Decoration::registerWeekElements(const Element::List &e, const QDate &d) { mWeekElements.insert(weekDate(d), e); return e; } Element::List Decoration::registerMonthElements(const Element::List &e, const QDate &d) { mMonthElements.insert(monthDate(d), e); return e; } Element::List Decoration::registerYearElements(const Element::List &e, const QDate &d) { mYearElements.insert(yearDate(d), e); return e; } Element::List Decoration::createDayElements(const QDate &) { return Element::List(); } Element::List Decoration::createWeekElements(const QDate &) { return Element::List(); } Element::List Decoration::createMonthElements(const QDate &) { return Element::List(); } Element::List Decoration::createYearElements(const QDate &) { return Element::List(); } QDate Decoration::weekDate(const QDate &date) { QDate result = date; return result.addDays(date.dayOfWeek() - 1); } QDate Decoration::monthDate(const QDate &date) { return QDate(date.year(), date.month(), 1); } QDate Decoration::yearDate(const QDate &date) { return QDate(date.year(), 1, 1); } 07070100000014000081A40000000200000002000000015F0BF3C900000E2C000000000000000000000000000000000000005400000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/calendardecoration.desktop[Desktop Entry] Name=Calendar Decoration Interface Name[ar]=واجهة زخرفة التّقاويم Name[ca]=Interfície de decoració del calendari Name[ca@valencia]=Interfície de decoració del calendari Name[cs]=Rozhraní dekorace kalendáře Name[da]=Kalenderdekorations-grænseflade Name[de]=Dekoration für den Kalender Name[en_GB]=Calendar Decoration Interface Name[es]=Interfaz de decoración del calendario Name[et]=Kalendri dekoratsiooni liides Name[eu]=Egutegia apaintzeko interfazea Name[fi]=Kalenteritekstin koristeluliitännäinen Name[fr]=Interface de décoration d'agenda Name[gl]=Interface de decoración de calendarios Name[ia]=Interfacie de decoration de Calendario Name[it]=Interfaccia di decorazione del calendario Name[ja]=カレンダー装飾インターフェース Name[ko]=달력 장식 인터페이스 Name[nb]=Grensesnitt til kalenderpynt Name[nl]=Interface van agendadecoratie Name[nn]=Grensesnitt til kalenderpynt Name[pl]=Interfejs do ozdób kalendarza Name[pt]=Interface de Decoração do Calendário Name[pt_BR]=Interface de decoração do calendário Name[ru]=Оформление календаря Name[sk]=Rozhranie dekorácie kalendára Name[sl]=Vmesnik za okraske koledarja Name[sr]=Сучеље за декорацију календара Name[sr@ijekavian]=Сучеље за декорацију календара Name[sr@ijekavianlatin]=Sučelje za dekoraciju kalendara Name[sr@latin]=Sučelje za dekoraciju kalendara Name[sv]=Gränssnitt för kalenderdekoration Name[tr]=Takvim Dekorasyon Arayüzü Name[uk]=Інтерфейс оздоблення календаря Name[x-test]=xxCalendar Decoration Interfacexx Name[zh_CN]=日历装饰界面 Name[zh_TW]=行事曆裝飾介面 Type=ServiceType X-KDE-ServiceType=Calendar/Decoration Comment=Calendar Decoration Plugin Comment[ar]=ملحقة زخرفة التّقاويم Comment[ca]=Connector de decoració del calendari Comment[ca@valencia]=Connector de decoració del calendari Comment[cs]=Modul dekorace kalendáře Comment[da]=Plugin til kalenderdekoration Comment[de]=Dekoration für den Kalender Comment[en_GB]=Calendar Decoration Plugin Comment[es]=Complemento de decoración del calendario Comment[et]=Kalendri dekoratsiooni plugin Comment[eu]=Egutegia apaintzeko plugina Comment[fi]=Kalenteritekstin koristeluliitännäinen Comment[fr]=Module de décoration d'agenda Comment[gl]=Complemento para a decoración do calendario Comment[ia]=Plug-in de decoration de Calendario Comment[it]=Estensione di decorazione del calendario Comment[ja]=カレンダー装飾プラグイン Comment[ko]=달력 장식 플러그인 Comment[nb]=Programtillegg for kalenderpynt Comment[nl]=Agendadecoratieplug-in Comment[nn]=Programtillegg for kalenderpynt Comment[pl]=Wtyczka do ozdób kalendarza Comment[pt]='Plugin' de Decoração do Calendário Comment[pt_BR]=Plugin para decoração do calendário Comment[ru]=Оформление календаря Comment[se]=Kaleandarčiŋaid lassemoduvla Comment[sk]=Modul pre skrášlenie kalendára Comment[sl]=Vstavek za okraske koledarja Comment[sr]=Прикључак за декорацију календара Comment[sr@ijekavian]=Прикључак за декорацију календара Comment[sr@ijekavianlatin]=Priključak za dekoraciju kalendara Comment[sr@latin]=Priključak za dekoraciju kalendara Comment[sv]=Insticksprogram för kalenderdekoration Comment[tr]=Takvim Dekorasyon Eklentisi Comment[uk]=Додаток прикрас календаря Comment[x-test]=xxCalendar Decoration Pluginxx Comment[zh_CN]=日历装饰插件 Comment[zh_TW]=行事曆裝飾附加元件 07070100000015000081A40000000200000002000000015F0BF3C900001C6E000000000000000000000000000000000000004E00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/calendardecoration.h/* Copyright (c) 2007 Loïc Corbasson <loic.corbasson@gmail.com> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef EVENTVIEWS_CALENDARDECORATION_H #define EVENTVIEWS_CALENDARDECORATION_H #include "eventviews_export.h" #include <CalendarSupport/Plugin> #include <QUrl> #include <QDate> #include <QPixmap> namespace EventViews { namespace CalendarDecoration { /** @class Element @brief Class for calendar decoration elements It provides entities like texts and pictures for a given date. Implementations can implement all functions or only a subset. */ class EVENTVIEWS_EXPORT Element : public QObject { Q_OBJECT public: typedef QList<Element *> List; explicit Element(const QString &id); ~Element() override; /** Return a name for easy identification. This will be used for example for internal configuration (position, etc.), so don't i18n it and make it unique for your decoration. */ Q_REQUIRED_RESULT virtual QString id() const; /** Description of element. */ Q_REQUIRED_RESULT virtual QString elementInfo() const; /** Return a short text for a given date, usually only a few words. */ Q_REQUIRED_RESULT virtual QString shortText(); /** Return a long text for a given date. This text can be of any length, but usually it will have one or a few lines. Can for example be used as a tool tip. */ Q_REQUIRED_RESULT virtual QString longText(); /** Return an extensive text for a given date. This text can be of any length, but usually it will have one or a few paragraphs. */ Q_REQUIRED_RESULT virtual QString extensiveText(); /** Return a pixmap for a given date and a given size. */ Q_REQUIRED_RESULT virtual QPixmap newPixmap(const QSize &); /** Return a URL pointing to more information about the content of the element. */ Q_REQUIRED_RESULT virtual QUrl url(); Q_SIGNALS: void gotNewPixmap(const QPixmap &); void gotNewShortText(const QString &); void gotNewLongText(const QString &); void gotNewExtensiveText(const QString &); void gotNewUrl(const QUrl &); protected: QString mId; }; /** This class provides a stored element, which contains all data for the given date/month/year. */ class EVENTVIEWS_EXPORT StoredElement : public Element { Q_OBJECT public: explicit StoredElement(const QString &id); StoredElement(const QString &id, const QString &shortText); StoredElement(const QString &id, const QString &shortText, const QString &longText); StoredElement(const QString &id, const QString &shortText, const QString &longText, const QString &extensiveText); StoredElement(const QString &id, const QPixmap &pixmap); virtual void setShortText(const QString &t); QString shortText() override; virtual void setLongText(const QString &t); QString longText() override; virtual void setExtensiveText(const QString &t); QString extensiveText() override; virtual void setPixmap(const QPixmap &p); virtual QPixmap pixmap(); virtual void setUrl(const QUrl &u); QUrl url() override; protected: QString mShortText; QString mLongText; QString mExtensiveText; QPixmap mPixmap; QUrl mUrl; }; /** @class Decoration @brief This class provides the interface for a date dependent decoration. The decoration is made of various decoration elements, which show a defined text/picture/widget for a given date. */ class EVENTVIEWS_EXPORT Decoration : public CalendarSupport::Plugin { public: static int interfaceVersion() { return 2; } static QString serviceType() { return QStringLiteral("Calendar/Decoration"); } typedef QList<Decoration *> List; Decoration(); ~Decoration() override; /** Return all Elements for the given day. */ virtual Element::List dayElements(const QDate &date); /** Return all elements for the week the given date belongs to. */ virtual Element::List weekElements(const QDate &d); /** Return all elements for the month the given date belongs to. */ virtual Element::List monthElements(const QDate &d); /** Return all elements for the year the given date belongs to. */ virtual Element::List yearElements(const QDate &d); protected: /** Register the given elements for the given date. They will be deleted when this object is destroyed. */ Element::List registerDayElements(const Element::List &e, const QDate &d); /** Register the given elements for the week the given date belongs to. They will be deleted when this object is destroyed. */ Element::List registerWeekElements(const Element::List &e, const QDate &d); /** Register the given elements for the month the given date belongs to. They will be deleted when this object is destroyed. */ Element::List registerMonthElements(const Element::List &e, const QDate &d); /** Register the given elements for the year the given date belongs to. They will be deleted when this object is destroyed. */ Element::List registerYearElements(const Element::List &e, const QDate &d); /** Create day elements for given date. */ virtual Element::List createDayElements(const QDate &); /** Create elements for the week the given date belongs to. */ virtual Element::List createWeekElements(const QDate &); /** Create elements for the month the given date belongs to. */ virtual Element::List createMonthElements(const QDate &); /** Create elements for the year the given date belongs to. */ virtual Element::List createYearElements(const QDate &); /** Map all dates of the same week to a single date. */ QDate weekDate(const QDate &date); /** Map all dates of the same month to a single date. */ QDate monthDate(const QDate &date); /** Map all dates of the same year to a single date. */ QDate yearDate(const QDate &date); private: QMap<QDate, Element::List> mDayElements; QMap<QDate, Element::List> mWeekElements; QMap<QDate, Element::List> mMonthElements; QMap<QDate, Element::List> mYearElements; }; class EVENTVIEWS_EXPORT DecorationFactory : public CalendarSupport::PluginFactory { Q_OBJECT public: Decoration *createPluginFactory() override = 0; }; } } #endif 07070100000016000081A40000000200000002000000015F0BF3C9000017D6000000000000000000000000000000000000004D00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/decorationlabel.cpp/* Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (C) 2007 Loïc Corbasson <loic.corbasson@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "decorationlabel.h" #include <QDesktopServices> #include <QMouseEvent> #include <QResizeEvent> using namespace EventViews; DecorationLabel::DecorationLabel(CalendarDecoration::Element *e, QWidget *parent) : QLabel(parent) , mDecorationElement(e) , mShortText(e->shortText()) , mLongText(e->longText()) , mExtensiveText(e->extensiveText()) { mPixmap = e->newPixmap(size()); mUrl = e->url(); setUrl(mUrl); connect(e, &CalendarDecoration::Element::gotNewExtensiveText, this, &DecorationLabel::setExtensiveText); connect(e, &CalendarDecoration::Element::gotNewLongText, this, &DecorationLabel::setLongText); connect(e, &CalendarDecoration::Element::gotNewPixmap, this, &DecorationLabel::setPixmap); connect(e, &CalendarDecoration::Element::gotNewShortText, this, &DecorationLabel::setShortText); connect(e, &CalendarDecoration::Element::gotNewUrl, this, &DecorationLabel::setUrl); squeezeContentsToLabel(); } DecorationLabel::DecorationLabel(const QString &shortText, const QString &longText, const QString &extensiveText, const QPixmap &pixmap, const QUrl &url, QWidget *parent) : QLabel(parent) , mShortText(shortText) , mLongText(longText) , mExtensiveText(extensiveText) , mPixmap(pixmap) { setUrl(url); squeezeContentsToLabel(); } DecorationLabel::~DecorationLabel() { delete mDecorationElement; } void DecorationLabel::mouseReleaseEvent(QMouseEvent *event) { QLabel::mouseReleaseEvent(event); switch (event->button()) { case Qt::LeftButton: if (!mUrl.isEmpty()) { QDesktopServices::openUrl(mUrl); setForegroundRole(QPalette::LinkVisited); } break; case Qt::MidButton: case Qt::RightButton: default: break; } } void DecorationLabel::resizeEvent(QResizeEvent *event) { mPixmap = mDecorationElement->newPixmap(event->size()); QLabel::resizeEvent(event); squeezeContentsToLabel(); } void DecorationLabel::setExtensiveText(const QString &text) { mExtensiveText = text; squeezeContentsToLabel(); } void DecorationLabel::setLongText(const QString &text) { mLongText = text; squeezeContentsToLabel(); } void DecorationLabel::setPixmap(const QPixmap &pixmap) { mPixmap = pixmap.scaled(size(), Qt::KeepAspectRatio); squeezeContentsToLabel(); } void DecorationLabel::setShortText(const QString &text) { mShortText = text; squeezeContentsToLabel(); } void DecorationLabel::setText(const QString &text) { setLongText(text); } void DecorationLabel::setUrl(const QUrl &url) { mUrl = url; QFont f = font(); if (url.isEmpty()) { setForegroundRole(QPalette::WindowText); f.setUnderline(false); #ifndef QT_NO_CURSOR setCursor(QCursor(Qt::ArrowCursor)); #endif } else { setForegroundRole(QPalette::Link); f.setUnderline(true); #ifndef QT_NO_CURSOR setCursor(QCursor(Qt::PointingHandCursor)); #endif } setFont(f); } void DecorationLabel::squeezeContentsToLabel() { if (!mAutomaticSqueeze) { // The content type to use has been set manually return; } QFontMetrics fm(fontMetrics()); int labelWidth = size().width(); int longTextWidth = fm.boundingRect(mLongText).width(); int extensiveTextWidth = fm.boundingRect(mExtensiveText).width(); if (!mPixmap.isNull()) { usePixmap(true); } else if ((!mExtensiveText.isEmpty()) && (extensiveTextWidth <= labelWidth)) { useExtensiveText(true); } else if ((!mLongText.isEmpty()) && (longTextWidth <= labelWidth)) { useLongText(true); } else { useShortText(true); } setAlignment(Qt::AlignCenter); setWordWrap(true); QSize msh = QLabel::minimumSizeHint(); msh.setHeight(fontMetrics().lineSpacing()); msh.setWidth(0); setMinimumSize(msh); setSizePolicy(sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding); } void DecorationLabel::useDefaultText() { mAutomaticSqueeze = false; squeezeContentsToLabel(); } void DecorationLabel::useExtensiveText(bool allowAutomaticSqueeze) { mAutomaticSqueeze = allowAutomaticSqueeze; QLabel::setText(mExtensiveText); setToolTip(QString()); } void DecorationLabel::useLongText(bool allowAutomaticSqueeze) { mAutomaticSqueeze = allowAutomaticSqueeze; QLabel::setText(mLongText); setToolTip(mExtensiveText.isEmpty() ? QString() : mExtensiveText); } void DecorationLabel::usePixmap(bool allowAutomaticSqueeze) { mAutomaticSqueeze = allowAutomaticSqueeze; QLabel::setPixmap(mPixmap); setToolTip(mExtensiveText.isEmpty() ? mLongText : mExtensiveText); } void DecorationLabel::useShortText(bool allowAutomaticSqueeze) { mAutomaticSqueeze = allowAutomaticSqueeze; QLabel::setText(mShortText); setToolTip(mExtensiveText.isEmpty() ? mLongText : mExtensiveText); } 07070100000017000081A40000000200000002000000015F0BF3C900000A1E000000000000000000000000000000000000004B00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/decorationlabel.h/* Copyright (c) 2000,2001,2003 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (C) 2007 Loïc Corbasson <loic.corbasson@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_DECORATIONLABEL_H #define EVENTVIEWS_DECORATIONLABEL_H #include "calendardecoration.h" #include <QLabel> namespace EventViews { class DecorationLabel : public QLabel { Q_OBJECT public: explicit DecorationLabel(EventViews::CalendarDecoration::Element *e, QWidget *parent = nullptr); explicit DecorationLabel(const QString &shortText, const QString &longText = QString(), const QString &extensiveText = QString(), const QPixmap &pixmap = QPixmap(), const QUrl &url = QUrl(), QWidget *parent = nullptr); ~DecorationLabel() override; public Q_SLOTS: void setExtensiveText(const QString &); void setLongText(const QString &); void setPixmap(const QPixmap &); void setShortText(const QString &); void setText(const QString &); void setUrl(const QUrl &); void useShortText(bool allowAutomaticSqueeze = false); void useLongText(bool allowAutomaticSqueeze = false); void useExtensiveText(bool allowAutomaticSqueeze = false); void usePixmap(bool allowAutomaticSqueeze = false); void useDefaultText(); protected: void resizeEvent(QResizeEvent *) override; void mouseReleaseEvent(QMouseEvent *) override; virtual void squeezeContentsToLabel(); bool mAutomaticSqueeze = true; EventViews::CalendarDecoration::Element *mDecorationElement = nullptr; QString mShortText, mLongText, mExtensiveText; QPixmap mPixmap; QUrl mUrl; }; } #endif 07070100000018000081A40000000200000002000000015F0BF3C9000037A2000000000000000000000000000000000000004800000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/timelabels.cpp/* Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (c) 2007 Bruno Virlet <bruno@virlet.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "timelabels.h" #include "agenda.h" #include "prefs.h" #include "timelabelszone.h" #include "timescaleconfigdialog.h" #include <KCalUtils/Stringify> #include <KLocalizedString> #include <QFrame> #include <QHelpEvent> #include <QIcon> #include <QMenu> #include <QPainter> #include <QPointer> #include <QToolTip> using namespace EventViews; TimeLabels::TimeLabels(const QTimeZone &zone, int rows, TimeLabelsZone *parent, Qt::WindowFlags f) : QFrame(parent, f) , mTimezone(zone) { mTimeLabelsZone = parent; mRows = rows; mMiniWidth = 0; mCellHeight = mTimeLabelsZone->preferences()->hourSize() * 4; setBackgroundRole(QPalette::Window); mMousePos = new QFrame(this); mMousePos->setLineWidth(1); mMousePos->setFrameStyle(QFrame::HLine | QFrame::Plain); mMousePos->setFixedSize(width(), 1); colorMousePos(); mAgenda = nullptr; setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); updateConfig(); } void TimeLabels::mousePosChanged(const QPoint &pos) { colorMousePos(); mMousePos->move(0, pos.y()); // The repaint somehow prevents that the red line leaves a black artifact when // moved down. It's not a full solution, though. repaint(); } void TimeLabels::showMousePos() { // touch screen have no mouse position mMousePos->show(); } void TimeLabels::hideMousePos() { mMousePos->hide(); } void TimeLabels::colorMousePos() { QPalette pal; pal.setColor(QPalette::Window, // for Oxygen mTimeLabelsZone->preferences()->agendaMarcusBainsLineLineColor()); pal.setColor(QPalette::WindowText, // for Plastique mTimeLabelsZone->preferences()->agendaMarcusBainsLineLineColor()); mMousePos->setPalette(pal); } void TimeLabels::setCellHeight(double height) { if (mCellHeight != height) { mCellHeight = height; updateGeometry(); } } QSize TimeLabels::minimumSizeHint() const { QSize sh = QFrame::sizeHint(); sh.setWidth(mMiniWidth); return sh; } static bool use12Clock() { const QString str = QLocale().timeFormat(); // 'A' or 'a' means am/pm is shown (and then 'h' uses 12-hour format) // but 'H' forces a 24-hour format anyway, even with am/pm shown. return str.contains(QLatin1Char('a'), Qt::CaseInsensitive) && !str.contains(QLatin1Char('H')); } /** updates widget's internal state */ void TimeLabels::updateConfig() { setFont(mTimeLabelsZone->preferences()->agendaTimeLabelsFont()); QString test = QStringLiteral("20"); if (use12Clock()) { test = QStringLiteral("12"); } mMiniWidth = fontMetrics().boundingRect(test).width(); if (use12Clock()) { test = QStringLiteral("pm"); } else { test = QStringLiteral("00"); } QFont sFont = font(); sFont.setPointSize(sFont.pointSize() / 2); QFontMetrics fmS(sFont); mMiniWidth += fmS.boundingRect(test).width() + frameWidth() * 2 + 4; /** Can happen if all resources are disabled */ if (!mAgenda) { return; } // update HourSize mCellHeight = mTimeLabelsZone->preferences()->hourSize() * 4; // If the agenda is zoomed out so that more than 24 would be shown, // the agenda only shows 24 hours, so we need to take the cell height // from the agenda, which is larger than the configured one! if (mCellHeight < 4 * mAgenda->gridSpacingY()) { mCellHeight = 4 * mAgenda->gridSpacingY(); } updateGeometry(); repaint(); } /** */ void TimeLabels::setAgenda(Agenda *agenda) { mAgenda = agenda; if (mAgenda) { connect(mAgenda, &Agenda::mousePosSignal, this, &TimeLabels::mousePosChanged); connect(mAgenda, &Agenda::enterAgenda, this, &TimeLabels::showMousePos); connect(mAgenda, &Agenda::leaveAgenda, this, &TimeLabels::hideMousePos); connect(mAgenda, &Agenda::gridSpacingYChanged, this, &TimeLabels::setCellHeight); } } int TimeLabels::yposToCell(const int ypos) const { const KCalendarCore::DateList datelist = mAgenda->dateList(); if (datelist.isEmpty()) { return 0; } const auto firstDay = QDateTime(datelist.first(), QTime(0, 0, 0), Qt::LocalTime).toUTC(); const int beginning // the hour we start drawing with = !mTimezone.isValid() ? 0 : (mTimezone.offsetFromUtc(firstDay) -mTimeLabelsZone->preferences()->timeZone().offsetFromUtc(firstDay)) / 3600; return static_cast<int>(ypos / mCellHeight) + beginning; } int TimeLabels::cellToHour(const int cell) const { int tCell = cell % 24; // handle different timezones if (tCell < 0) { tCell += 24; } // handle 24h and am/pm time formats if (use12Clock()) { if (tCell == 0) { tCell = 12; } if (tCell < 0) { tCell += 24; } if (tCell > 12) { tCell %= 12; if (tCell == 0) { tCell = 12; } } } return tCell; } QString TimeLabels::cellToSuffix(const int cell) const { //TODO: rewrite this using QTime's time formats. "am/pm" doesn't make sense // in some locale's QString suffix; if (use12Clock()) { if ((cell / 12) % 2 != 0) { suffix = QStringLiteral("pm"); } else { suffix = QStringLiteral("am"); } } else { suffix = QStringLiteral("00"); } return suffix; } /** This is called in response to repaint() */ void TimeLabels::paintEvent(QPaintEvent *) { if (!mAgenda) { return; } const KCalendarCore::DateList datelist = mAgenda->dateList(); if (datelist.isEmpty()) { return; } QPainter p(this); const int ch = height(); // We won't paint parts that aren't visible const int cy = -y(); // y() returns a negative value. const auto firstDay = QDateTime(datelist.first(), QTime(0, 0, 0), Qt::LocalTime).toUTC(); const int beginning = !mTimezone.isValid() ? 0 : (mTimezone.offsetFromUtc(firstDay) -mTimeLabelsZone->preferences()->timeZone().offsetFromUtc(firstDay)) / 3600; // bug: the parameters cx and cw are the areas that need to be // redrawn, not the area of the widget. unfortunately, this // code assumes the latter... // now, for a workaround... const int cx = frameWidth() * 2; const int cw = width(); // end of workaround int cell = yposToCell(cy); double y = (cell - beginning) * mCellHeight; QFontMetrics fm = fontMetrics(); QString hour; int timeHeight = fm.ascent(); QFont hourFont = mTimeLabelsZone->preferences()->agendaTimeLabelsFont(); p.setFont(font()); //TODO: rewrite this using QTime's time formats. "am/pm" doesn't make sense // in some locale's QString suffix; if (!use12Clock()) { suffix = QStringLiteral("00"); } else { suffix = QStringLiteral("am"); } // We adjust the size of the hour font to keep it reasonable if (timeHeight > mCellHeight) { timeHeight = static_cast<int>(mCellHeight - 1); int pointS = hourFont.pointSize(); while (pointS > 4) { // TODO: use smallestReadableFont() when added to kdelibs hourFont.setPointSize(pointS); fm = QFontMetrics(hourFont); if (fm.ascent() < mCellHeight) { break; } --pointS; } fm = QFontMetrics(hourFont); timeHeight = fm.ascent(); } //timeHeight -= (timeHeight/4-2); QFont suffixFont = hourFont; suffixFont.setPointSize(suffixFont.pointSize() / 2); QFontMetrics fmS(suffixFont); const int startW = cw - frameWidth() - 2; const int tw2 = fmS.boundingRect(suffix).width(); const int divTimeHeight = (timeHeight - 1) / 2 - 1; //testline //p->drawLine(0,0,0,contentsHeight()); while (y < cy + ch + mCellHeight) { QColor lineColor, textColor; textColor = palette().color(QPalette::WindowText); if (cell < 0 || cell >= 24) { textColor.setAlphaF(0.5); } lineColor = textColor; lineColor.setAlphaF(lineColor.alphaF() / 5.); p.setPen(lineColor); // hour, full line p.drawLine(cx, int(y), cw + 2, int(y)); // set the hour and suffix from the cell hour.setNum(cellToHour(cell)); suffix = cellToSuffix(cell); // draw the time label p.setPen(textColor); const int timeWidth = fm.boundingRect(hour).width(); int offset = startW - timeWidth - tw2 - 1; p.setFont(hourFont); p.drawText(offset, static_cast<int>(y + timeHeight), hour); p.setFont(suffixFont); offset = startW - tw2; p.drawText(offset, static_cast<int>(y + timeHeight - divTimeHeight), suffix); // increment indices y += mCellHeight; cell++; } } QSize TimeLabels::sizeHint() const { return QSize(mMiniWidth, mRows * mCellHeight); } void TimeLabels::contextMenuEvent(QContextMenuEvent *event) { Q_UNUSED(event); QMenu popup(this); QAction *editTimeZones = popup.addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("&Add Timezones...")); QAction *removeTimeZone = popup.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("&Remove Timezone %1", i18n(mTimezone.id().constData()))); if (!mTimezone.isValid() || !mTimeLabelsZone->preferences()->timeScaleTimezones().count() || mTimezone == mTimeLabelsZone->preferences()->timeZone()) { removeTimeZone->setEnabled(false); } QAction *activatedAction = popup.exec(QCursor::pos()); if (activatedAction == editTimeZones) { QPointer<TimeScaleConfigDialog> dialog = new TimeScaleConfigDialog(mTimeLabelsZone->preferences(), this); if (dialog->exec() == QDialog::Accepted) { mTimeLabelsZone->reset(); } delete dialog; } else if (activatedAction == removeTimeZone) { QStringList list = mTimeLabelsZone->preferences()->timeScaleTimezones(); list.removeAll(QString::fromUtf8(mTimezone.id())); mTimeLabelsZone->preferences()->setTimeScaleTimezones(list); mTimeLabelsZone->preferences()->writeConfig(); mTimeLabelsZone->reset(); hide(); deleteLater(); } } QTimeZone TimeLabels::timeZone() const { return mTimezone; } QString TimeLabels::header() const { return i18n(mTimezone.id().constData()); } QString TimeLabels::headerToolTip() const { QDateTime now = QDateTime::currentDateTime(); QString toolTip; toolTip += QLatin1String("<qt>"); toolTip += i18nc("title for timezone info, the timezone id and utc offset", "<b>%1 (%2)</b>", i18n(mTimezone.id().constData()), KCalUtils::Stringify::tzUTCOffsetStr(mTimezone)); toolTip += QLatin1String("<hr>"); toolTip += i18nc("heading for timezone display name", "<i>Name:</i> %1", mTimezone.displayName(now, QTimeZone::LongName)); toolTip += QLatin1String("<br/>"); if (mTimezone.country() != QLocale::AnyCountry) { toolTip += i18nc("heading for timezone country", "<i>Country:</i> %1", QLocale::countryToString(mTimezone.country())); toolTip += QLatin1String("<br/>"); } auto abbreviations = QStringLiteral(" "); const auto lst = mTimezone.transitions(now, now.addYears(1)); for (const auto &transition : lst) { abbreviations += transition.abbreviation; abbreviations += QLatin1String(", "); } abbreviations.chop(7); if (!abbreviations.isEmpty()) { toolTip += i18nc("heading for comma-separated list of timezone abbreviations", "<i>Abbreviations:</i>"); toolTip += abbreviations; toolTip += QLatin1String("<br/>"); } const QString timeZoneComment(mTimezone.comment()); if (!timeZoneComment.isEmpty()) { toolTip += i18nc("heading for the timezone comment", "<i>Comment:</i> %1", timeZoneComment); } toolTip += QLatin1String("</qt>"); return toolTip; } bool TimeLabels::event(QEvent *event) { if (event->type() == QEvent::ToolTip) { QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event); const int cell = yposToCell(helpEvent->pos().y()); QString toolTip; toolTip += QLatin1String("<qt>"); toolTip += i18nc("[hour of the day][am/pm/00] [timezone id (timezone-offset)]", "%1%2<br/>%3 (%4)", cellToHour(cell), cellToSuffix(cell), i18n(mTimezone.id().constData()), KCalUtils::Stringify::tzUTCOffsetStr(mTimezone)); toolTip += QLatin1String("</qt>"); QToolTip::showText(helpEvent->globalPos(), toolTip, this); return true; } return QWidget::event(event); } 07070100000019000081A40000000200000002000000015F0BF3C900000BAF000000000000000000000000000000000000004600000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/timelabels.h/* Copyright (c) 2000,2001,2003 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (c) 2007 Bruno Virlet <bruno@virlet.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_TIMELABELS_H #define EVENTVIEWS_TIMELABELS_H #include <QTimeZone> #include <QFrame> namespace EventViews { class Agenda; class TimeLabelsZone; class Prefs; typedef QSharedPointer<Prefs> PrefsPtr; class TimeLabels : public QFrame { Q_OBJECT public: typedef QList<TimeLabels *> List; TimeLabels(const QTimeZone &zone, int rows, TimeLabelsZone *parent = nullptr, Qt::WindowFlags f = {}); /** updates widget's internal state */ void updateConfig(); /** */ void setAgenda(Agenda *agenda); /** */ void paintEvent(QPaintEvent *e) override; /** */ void contextMenuEvent(QContextMenuEvent *event) override; /** Returns the time zone of this label */ Q_REQUIRED_RESULT QTimeZone timeZone() const; /** Return string which can be used as a header for the time label. */ Q_REQUIRED_RESULT QString header() const; /** Return string which can be used as a tool tip for the header. */ Q_REQUIRED_RESULT QString headerToolTip() const; QSize sizeHint() const override; QSize minimumSizeHint() const override; /** */ bool event(QEvent *event) override; private: Q_REQUIRED_RESULT int yposToCell(const int ypos) const; Q_REQUIRED_RESULT int cellToHour(const int cell) const; Q_REQUIRED_RESULT QString cellToSuffix(const int cell) const; /** update the position of the marker showing the mouse position */ void mousePosChanged(const QPoint &pos); void showMousePos(); void hideMousePos(); void setCellHeight(double height); void colorMousePos(); QTimeZone mTimezone; int mRows; double mCellHeight; int mMiniWidth; Agenda *mAgenda = nullptr; TimeLabelsZone *mTimeLabelsZone = nullptr; QFrame *mMousePos = nullptr; // shows a marker for the current mouse position in y direction }; } #endif 0707010000001A000081A40000000200000002000000015F0BF3C90000151A000000000000000000000000000000000000004C00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/timelabelszone.cpp/* Copyright (c) 2007 Bruno Virlet <bruno@virlet.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "timelabelszone.h" #include "agenda.h" #include "agendaview.h" #include "prefs.h" #include "timelabels.h" #include <QHBoxLayout> #include <QScrollArea> #include <QScrollBar> using namespace EventViews; TimeLabelsZone::TimeLabelsZone(QWidget *parent, const PrefsPtr &preferences, Agenda *agenda) : QWidget(parent) , mAgenda(agenda) , mPrefs(preferences) , mParent(qobject_cast<AgendaView * >(parent)) { mTimeLabelsLayout = new QHBoxLayout(this); mTimeLabelsLayout->setContentsMargins(0, 0, 0, 0); mTimeLabelsLayout->setSpacing(0); init(); } void TimeLabelsZone::reset() { for (QScrollArea *label : qAsConst(mTimeLabelsList)) { label->hide(); label->deleteLater(); } mTimeLabelsList.clear(); init(); // Update some related geometry from the agenda view updateAll(); if (mParent) { mParent->updateTimeBarWidth(); mParent->createDayLabels(true); } } void TimeLabelsZone::init() { QStringList seenTimeZones(QString::fromUtf8(mPrefs->timeZone().id())); addTimeLabels(mPrefs->timeZone()); const auto lst = mPrefs->timeScaleTimezones(); for (const QString &zoneStr : lst) { if (!seenTimeZones.contains(zoneStr)) { auto zone = QTimeZone(zoneStr.toUtf8()); if (zone.isValid()) { addTimeLabels(zone); seenTimeZones += zoneStr; } } } } void TimeLabelsZone::addTimeLabels(const QTimeZone &zone) { QScrollArea *area = new QScrollArea(this); TimeLabels *labels = new TimeLabels(zone, 24, this); mTimeLabelsList.prepend(area); area->setWidgetResizable(true); area->setWidget(labels); area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); area->setBackgroundRole(QPalette::Window); area->setFrameStyle(QFrame::NoFrame); area->show(); mTimeLabelsLayout->insertWidget(0, area); setupTimeLabel(area); } void TimeLabelsZone::setupTimeLabel(QScrollArea *area) { if (mAgenda && mAgenda->verticalScrollBar()) { // Scrolling the agenda will scroll the timelabel connect(mAgenda->verticalScrollBar(), &QAbstractSlider::valueChanged, area->verticalScrollBar(), &QAbstractSlider::setValue); // and vice-versa. ( this won't loop ) connect(area->verticalScrollBar(), &QAbstractSlider::valueChanged, mAgenda->verticalScrollBar(), &QAbstractSlider::setValue); area->verticalScrollBar()->setValue(mAgenda->verticalScrollBar()->value()); } TimeLabels *timeLabels = static_cast<TimeLabels *>(area->widget()); timeLabels->setAgenda(mAgenda); // timelabel's scroll is just a slave, this shouldn't be here // if ( mParent ) { // connect( area->verticalScrollBar(), SIGNAL(valueChanged(int)), // mParent, SLOT(setContentsPos(int)) ); // } } int TimeLabelsZone::preferedTimeLabelsWidth() const { if (mTimeLabelsList.isEmpty()) { return 0; } else { return mTimeLabelsList.first()->widget()->sizeHint().width(); } } void TimeLabelsZone::updateAll() { for (QScrollArea *area : qAsConst(mTimeLabelsList)) { TimeLabels *timeLabel = static_cast<TimeLabels *>(area->widget()); timeLabel->updateConfig(); } } QList<QScrollArea *> TimeLabelsZone::timeLabels() const { return mTimeLabelsList; } void TimeLabelsZone::setAgendaView(AgendaView *agendaView) { mParent = agendaView; mAgenda = agendaView ? agendaView->agenda() : nullptr; for (QScrollArea *timeLabel : qAsConst(mTimeLabelsList)) { setupTimeLabel(timeLabel); } } void TimeLabelsZone::updateTimeLabelsPosition() { if (mAgenda) { const auto lst = timeLabels(); for (QScrollArea *area : lst) { TimeLabels *label = static_cast<TimeLabels *>(area->widget()); const int adjustment = mAgenda->contentsY(); // y() is the offset to our parent (QScrollArea) // and gets negative as we scroll if (adjustment != -label->y()) { area->verticalScrollBar()->setValue(adjustment); } } } } PrefsPtr TimeLabelsZone::preferences() const { return mPrefs; } void TimeLabelsZone::setPreferences(const PrefsPtr &prefs) { if (prefs != mPrefs) { mPrefs = prefs; } } 0707010000001B000081A40000000200000002000000015F0BF3C900000943000000000000000000000000000000000000004A00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/timelabelszone.h/* Copyright (c) 2007 Bruno Virlet <bruno@virlet.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_TIMELABELSZONE_H #define EVENTVIEWS_TIMELABELSZONE_H #include <QWidget> class QHBoxLayout; class QScrollArea; class QTimeZone; namespace EventViews { class Agenda; class AgendaView; class Prefs; typedef QSharedPointer<Prefs> PrefsPtr; class TimeLabelsZone : public QWidget { Q_OBJECT public: explicit TimeLabelsZone(QWidget *parent, const PrefsPtr &preferences, Agenda *agenda = nullptr); /** Add a new time label with the given time zone. If @p zone is not valid, use the display time zone. */ void addTimeLabels(const QTimeZone &zone); /** Returns the best width for each TimeLabels widget */ Q_REQUIRED_RESULT int preferedTimeLabelsWidth() const; void updateAll(); void reset(); void init(); void setAgendaView(AgendaView *agenda); Q_REQUIRED_RESULT QList<QScrollArea *> timeLabels() const; void setPreferences(const PrefsPtr &prefs); Q_REQUIRED_RESULT PrefsPtr preferences() const; /** Checks how much agenda is scrolled relative to it's QScrollArea and makes each TimeLabels scroll that amount */ void updateTimeLabelsPosition(); private: void setupTimeLabel(QScrollArea *area); Agenda *mAgenda = nullptr; PrefsPtr mPrefs; AgendaView *mParent = nullptr; QHBoxLayout *mTimeLabelsLayout = nullptr; QList<QScrollArea *> mTimeLabelsList; }; } #endif 0707010000001C000081A40000000200000002000000015F0BF3C900001C39000000000000000000000000000000000000005300000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/timescaleconfigdialog.cpp/* Copyright (c) 2007 Bruno Virlet <bruno@virlet.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "timescaleconfigdialog.h" #include "prefs.h" #include <KCalUtils/Stringify> #include <KLocalizedString> #include <QIcon> #include <QDialogButtonBox> #include <QPushButton> #include <QTimeZone> #include <QVBoxLayout> using namespace EventViews; class Q_DECL_HIDDEN TimeScaleConfigDialog::Private { public: Private(TimeScaleConfigDialog *parent, const PrefsPtr &preferences) : q(parent) , mPreferences(preferences) { } public: TimeScaleConfigDialog *const q; PrefsPtr mPreferences; }; enum { TimeZoneNameRole = Qt::UserRole }; typedef QPair<QString, QByteArray> TimeZoneNamePair; static QString tzWithUTC(const QByteArray &zoneId) { auto tz = QTimeZone(zoneId); return QStringLiteral("%1 (%2)"). arg(i18n(zoneId.constData()), KCalUtils::Stringify::tzUTCOffsetStr(tz)); } TimeScaleConfigDialog::TimeScaleConfigDialog(const PrefsPtr &preferences, QWidget *parent) : QDialog(parent) , d(new Private(this, preferences)) { setWindowTitle(i18nc("@title:window", "Timezone")); QVBoxLayout *mainLayout = new QVBoxLayout(this); setModal(true); QWidget *mainwidget = new QWidget(this); setupUi(mainwidget); mainLayout->addWidget(mainwidget); QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::rejected, this, &TimeScaleConfigDialog::reject); mainLayout->addWidget(buttonBox); QStringList shownTimeZones(QString::fromUtf8(d->mPreferences->timeZone().id())); shownTimeZones += d->mPreferences->timeScaleTimezones(); shownTimeZones.removeDuplicates(); QList<TimeZoneNamePair> availList, selList; const auto zoneIds = QTimeZone::availableTimeZoneIds(); for (const auto &zoneId : qAsConst(zoneIds)) { // do not list timezones already shown if (!shownTimeZones.contains(QString::fromUtf8(zoneId))) { availList.append(TimeZoneNamePair(tzWithUTC(zoneId), zoneId)); } else { selList.append(TimeZoneNamePair(tzWithUTC(zoneId), zoneId)); } } std::sort(availList.begin(), availList.end()); for (const TimeZoneNamePair &item : qAsConst(availList)) { zoneCombo->addItem(item.first, item.second); } zoneCombo->setCurrentIndex(0); addButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); removeButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); upButton->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); downButton->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); connect(addButton, &QPushButton::clicked, this, &TimeScaleConfigDialog::add); connect(removeButton, &QPushButton::clicked, this, &TimeScaleConfigDialog::remove); connect(upButton, &QPushButton::clicked, this, &TimeScaleConfigDialog::up); connect(downButton, &QPushButton::clicked, this, &TimeScaleConfigDialog::down); connect(okButton, &QPushButton::clicked, this, &TimeScaleConfigDialog::okClicked); connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &TimeScaleConfigDialog::reject); connect(listWidget, &QListWidget::currentItemChanged, this, &TimeScaleConfigDialog::slotUpdateButton); for (const TimeZoneNamePair &item : qAsConst(selList)) { QListWidgetItem *widgetItem = new QListWidgetItem(item.first); widgetItem->setData(TimeZoneNameRole, item.second); listWidget->addItem(widgetItem); } slotUpdateButton(); } TimeScaleConfigDialog::~TimeScaleConfigDialog() { delete d; } void TimeScaleConfigDialog::slotUpdateButton() { removeButton->setEnabled(listWidget->currentItem()); const bool numberElementMoreThanOneElement = (listWidget->count() > 1); upButton->setEnabled(numberElementMoreThanOneElement && (listWidget->currentRow() >= 1)); downButton->setEnabled(numberElementMoreThanOneElement && (listWidget->currentRow() < listWidget->count() - 1)); } void TimeScaleConfigDialog::okClicked() { d->mPreferences->setTimeScaleTimezones(zones()); d->mPreferences->writeConfig(); accept(); } void TimeScaleConfigDialog::add() { // Do not add duplicates if (zoneCombo->currentIndex() >= 0) { const int numberItem(listWidget->count()); for (int i = 0; i < numberItem; ++i) { if (listWidget->item(i)->data(TimeZoneNameRole).toString() == zoneCombo->itemData(zoneCombo->currentIndex(), TimeZoneNameRole).toString()) { return; } } QListWidgetItem *item = new QListWidgetItem(zoneCombo->currentText()); item->setData(TimeZoneNameRole, zoneCombo->itemData(zoneCombo->currentIndex(), TimeZoneNameRole).toString()); listWidget->addItem(item); zoneCombo->removeItem(zoneCombo->currentIndex()); } slotUpdateButton(); } void TimeScaleConfigDialog::remove() { zoneCombo->insertItem(0, listWidget->currentItem()->text(), zoneCombo->itemData(zoneCombo->currentIndex(), TimeZoneNameRole).toString()); delete listWidget->takeItem(listWidget->currentRow()); slotUpdateButton(); } void TimeScaleConfigDialog::up() { int row = listWidget->currentRow(); QListWidgetItem *item = listWidget->takeItem(row); listWidget->insertItem(qMax(row - 1, 0), item); listWidget->setCurrentRow(qMax(row - 1, 0)); } void TimeScaleConfigDialog::down() { int row = listWidget->currentRow(); QListWidgetItem *item = listWidget->takeItem(row); listWidget->insertItem(qMin(row + 1, listWidget->count()), item); listWidget->setCurrentRow(qMin(row + 1, listWidget->count() - 1)); } QStringList TimeScaleConfigDialog::zones() const { QStringList list; const int count = listWidget->count(); list.reserve(count); for (int i = 0; i < count; ++i) { list << listWidget->item(i)->data(TimeZoneNameRole).toString(); } return list; } 0707010000001D000081A40000000200000002000000015F0BF3C900000651000000000000000000000000000000000000005100000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/timescaleconfigdialog.h/* Copyright (c) 2007 Bruno Virlet <bruno@virlet.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_TIMESCALECONFIGDIALOG_H #define EVENTVIEWS_TIMESCALECONFIGDIALOG_H #include "ui_timescaleedit_base.h" #include <QDialog> namespace EventViews { class Prefs; typedef QSharedPointer<Prefs> PrefsPtr; class TimeScaleConfigDialog : public QDialog, private Ui::TimeScaleEditWidget { Q_OBJECT public: TimeScaleConfigDialog(const PrefsPtr &preferences, QWidget *parent); ~TimeScaleConfigDialog(); private: void add(); void remove(); void up(); void down(); void okClicked(); void slotUpdateButton(); QStringList zones() const; private: class Private; Private *const d; }; } #endif 0707010000001E000081A40000000200000002000000015F0BF3C9000007B4000000000000000000000000000000000000004F00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/timescaleedit_base.ui<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>TimeScaleEditWidget</class> <widget class="QWidget" name="TimeScaleEditWidget"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>400</width> <height>320</height> </rect> </property> <layout class="QVBoxLayout"> <property name="leftMargin"> <number>0</number> </property> <property name="topMargin"> <number>0</number> </property> <property name="rightMargin"> <number>0</number> </property> <property name="bottomMargin"> <number>0</number> </property> <item> <widget class="QComboBox" name="zoneCombo"/> </item> <item> <layout class="QHBoxLayout"> <item> <widget class="QListWidget" name="listWidget"/> </item> <item> <layout class="QVBoxLayout"> <item> <widget class="QPushButton" name="addButton"> <property name="text"> <string/> </property> </widget> </item> <item> <widget class="QPushButton" name="upButton"> <property name="text"> <string/> </property> </widget> </item> <item> <widget class="QPushButton" name="downButton"> <property name="text"> <string/> </property> </widget> </item> <item> <widget class="QPushButton" name="removeButton"> <property name="text"> <string/> </property> </widget> </item> <item> <spacer> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> <height>40</height> </size> </property> </spacer> </item> </layout> </item> </layout> </item> </layout> </widget> <resources/> <connections/> </ui> 0707010000001F000081A40000000200000002000000015F0BF3C90000170F000000000000000000000000000000000000004A00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/viewcalendar.cpp/* * Copyright (c) 2014 Sandro Knauß <knauss@kolabsys.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * As a special exception, permission is given to link this program * with any edition of Qt, and distribute the resulting executable, * without including the source code for Qt in the source distribution. */ #include "viewcalendar.h" #include "agendaview.h" #include "helper.h" #include "calendarview_debug.h" #include <CalendarSupport/Utils> using namespace EventViews; ViewCalendar::~ViewCalendar() { } MultiViewCalendar::~MultiViewCalendar() { } KCalendarCore::Calendar::Ptr MultiViewCalendar::getCalendar() const { return KCalendarCore::Calendar::Ptr(); } KCalendarCore::Incidence::List MultiViewCalendar::incidences() const { KCalendarCore::Incidence::List list; for (const ViewCalendar::Ptr &cal : qAsConst(mSubCalendars)) { if (cal->getCalendar()) { list += cal->getCalendar()->incidences(); } } return list; } int MultiViewCalendar::calendars() const { return mSubCalendars.size(); } ViewCalendar::Ptr MultiViewCalendar::findCalendar(const KCalendarCore::Incidence::Ptr &incidence) const { for (const ViewCalendar::Ptr &cal : qAsConst(mSubCalendars)) { if (cal->isValid(incidence)) { return cal; } } return ViewCalendar::Ptr(); } ViewCalendar::Ptr MultiViewCalendar::findCalendar(const QString &incidenceIdentifier) const { for (const ViewCalendar::Ptr &cal : qAsConst(mSubCalendars)) { if (cal->isValid(incidenceIdentifier)) { return cal; } } return ViewCalendar::Ptr(); } void MultiViewCalendar::addCalendar(const ViewCalendar::Ptr &calendar) { if (!mSubCalendars.contains(calendar)) { mSubCalendars.append(calendar); } } void MultiViewCalendar::setETMCalendar(const Akonadi::ETMCalendar::Ptr &calendar) { if (!mETMCalendar) { mETMCalendar = AkonadiViewCalendar::Ptr(new AkonadiViewCalendar); mETMCalendar->mAgendaView = mAgendaView; } mETMCalendar->mCalendar = calendar; addCalendar(mETMCalendar); } QString MultiViewCalendar::displayName(const KCalendarCore::Incidence::Ptr &incidence) const { ViewCalendar::Ptr cal = findCalendar(incidence); if (cal) { return cal->displayName(incidence); } return QString(); } QString MultiViewCalendar::iconForIncidence(const KCalendarCore::Incidence::Ptr &incidence) const { ViewCalendar::Ptr cal = findCalendar(incidence); if (cal) { return cal->iconForIncidence(incidence); } return QString(); } bool MultiViewCalendar::isValid(const KCalendarCore::Incidence::Ptr &incidence) const { ViewCalendar::Ptr cal = findCalendar(incidence); return cal; } bool MultiViewCalendar::isValid(const QString &incidenceIdentifier) const { ViewCalendar::Ptr cal = findCalendar(incidenceIdentifier); return cal; } QColor MultiViewCalendar::resourceColor(const KCalendarCore::Incidence::Ptr &incidence) const { ViewCalendar::Ptr cal = findCalendar(incidence); if (cal) { return cal->resourceColor(incidence); } return QColor(); } Akonadi::Item MultiViewCalendar::item(const KCalendarCore::Incidence::Ptr &incidence) const { if (mETMCalendar->isValid(incidence)) { return mETMCalendar->item(incidence); } return Akonadi::Item(); } AkonadiViewCalendar::~AkonadiViewCalendar() { } bool AkonadiViewCalendar::isValid(const KCalendarCore::Incidence::Ptr &incidence) const { if (!mCalendar) { return false; } if (item(incidence).isValid()) { return true; } return false; } bool AkonadiViewCalendar::isValid(const QString &incidenceIdentifier) const { if (!mCalendar) { return false; } return !mCalendar->incidence(incidenceIdentifier).isNull(); } Akonadi::Item AkonadiViewCalendar::item(const KCalendarCore::Incidence::Ptr &incidence) const { if (!mCalendar || !incidence) { return Akonadi::Item(); } bool ok = false; Akonadi::Item::Id id = incidence->customProperty("VOLATILE", "AKONADI-ID").toLongLong(&ok); if (id == -1 || !ok) { id = mCalendar->item(incidence).id(); if (id == -1) { // Ok, we really don't know the ID, give up. qCWarning(CALENDARVIEW_LOG) << "Item is invalid. uid = " << incidence->instanceIdentifier(); return Akonadi::Item(); } return mCalendar->item(incidence->instanceIdentifier()); } return mCalendar->item(id); } QString AkonadiViewCalendar::displayName(const KCalendarCore::Incidence::Ptr &incidence) const { return CalendarSupport::displayName(mCalendar.data(), item(incidence).parentCollection()); } QColor AkonadiViewCalendar::resourceColor(const KCalendarCore::Incidence::Ptr &incidence) const { return EventViews::resourceColor(item(incidence), mAgendaView->preferences()); } QString AkonadiViewCalendar::iconForIncidence(const KCalendarCore::Incidence::Ptr &incidence) const { return mAgendaView->iconForItem(item(incidence)); } KCalendarCore::Calendar::Ptr AkonadiViewCalendar::getCalendar() const { return mCalendar; } 07070100000020000081A40000000200000002000000015F0BF3C900000FEA000000000000000000000000000000000000004800000000eventviews-VERSIONgit.20200713T074025~752bb43/src/agenda/viewcalendar.h/* * Copyright (c) 2014 Sandro Knauß <knauss@kolabsys.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * As a special exception, permission is given to link this program * with any edition of Qt, and distribute the resulting executable, * without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_VIEWCALENDAR_H #define EVENTVIEWS_VIEWCALENDAR_H #include "eventviews_export.h" #include <AkonadiCore/Item> #include <Akonadi/Calendar/ETMCalendar> #include <KCalendarCore/Incidence> #include <QColor> #include <QList> namespace EventViews { class AgendaView; class EVENTVIEWS_EXPORT ViewCalendar { public: typedef QSharedPointer<ViewCalendar> Ptr; virtual ~ViewCalendar(); virtual bool isValid(const KCalendarCore::Incidence::Ptr &incidence) const = 0; virtual bool isValid(const QString &incidenceIdentifier) const = 0; virtual QString displayName(const KCalendarCore::Incidence::Ptr &incidence) const = 0; virtual QColor resourceColor(const KCalendarCore::Incidence::Ptr &incidence) const = 0; virtual QString iconForIncidence(const KCalendarCore::Incidence::Ptr &incidence) const = 0; virtual KCalendarCore::Calendar::Ptr getCalendar() const = 0; }; class AkonadiViewCalendar : public ViewCalendar { public: typedef QSharedPointer<AkonadiViewCalendar> Ptr; ~AkonadiViewCalendar() override; bool isValid(const KCalendarCore::Incidence::Ptr &incidence) const override; bool isValid(const QString &incidenceIdentifier) const override; QString displayName(const KCalendarCore::Incidence::Ptr &incidence) const override; QColor resourceColor(const KCalendarCore::Incidence::Ptr &incidence) const override; QString iconForIncidence(const KCalendarCore::Incidence::Ptr &incidence) const override; Akonadi::Item item(const KCalendarCore::Incidence::Ptr &incidence) const; KCalendarCore::Calendar::Ptr getCalendar() const override; Akonadi::ETMCalendar::Ptr mCalendar; AgendaView *mAgendaView = nullptr; }; class MultiViewCalendar : public ViewCalendar { public: typedef QSharedPointer<MultiViewCalendar> Ptr; ~MultiViewCalendar() override; ViewCalendar::Ptr findCalendar(const KCalendarCore::Incidence::Ptr &incidence) const; ViewCalendar::Ptr findCalendar(const QString &incidenceIdentifier) const; Q_REQUIRED_RESULT bool isValid(const KCalendarCore::Incidence::Ptr &incidence) const override; Q_REQUIRED_RESULT bool isValid(const QString &incidenceIdentifier) const override; Q_REQUIRED_RESULT QString displayName(const KCalendarCore::Incidence::Ptr &incidence) const override; Q_REQUIRED_RESULT QColor resourceColor(const KCalendarCore::Incidence::Ptr &incidence) const override; Q_REQUIRED_RESULT QString iconForIncidence(const KCalendarCore::Incidence::Ptr &incidence) const override; Q_REQUIRED_RESULT Akonadi::Item item(const KCalendarCore::Incidence::Ptr &incidence) const; void addCalendar(const ViewCalendar::Ptr &calendar); void setETMCalendar(const Akonadi::ETMCalendar::Ptr &calendar); int calendars() const; Q_REQUIRED_RESULT KCalendarCore::Calendar::Ptr getCalendar() const override; Q_REQUIRED_RESULT KCalendarCore::Incidence::List incidences() const; AgendaView *mAgendaView = nullptr; AkonadiViewCalendar::Ptr mETMCalendar; QList<ViewCalendar::Ptr> mSubCalendars; }; } #endif 07070100000021000081A40000000200000002000000015F0BF3C900004B9D000000000000000000000000000000000000004000000000eventviews-VERSIONgit.20200713T074025~752bb43/src/eventview.cpp/* Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com Author: Sergio Martins, sergio.martins@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "eventview_p.h" #include "prefs.h" #include <EntityTreeModel> #include <CalendarSupport/CollectionSelection> #include <CalendarSupport/KCalPrefs> #include <CalendarSupport/Utils> #include <EntityDisplayAttribute> #include <ETMViewStateSaver> #include <KCalendarCore/Todo> #include <KCalendarCore/CalFilter> #include <KCalUtils/RecurrenceActions> #include <KHolidays/HolidayRegion> #include <KCheckableProxyModel> #include <KGuiItem> #include <KLocalizedString> #include <KRandom> #include <KViewStateMaintainer> #include <KRearrangeColumnsProxyModel> #include "calendarview_debug.h" #include <QApplication> #include <QKeyEvent> #include <QSortFilterProxyModel> using namespace KCalendarCore; using namespace EventViews; using namespace Akonadi; CalendarSupport::CollectionSelection *EventViewPrivate::sGlobalCollectionSelection = nullptr; /* static */ void EventView::setGlobalCollectionSelection(CalendarSupport::CollectionSelection *s) { EventViewPrivate::sGlobalCollectionSelection = s; } EventView::EventView(QWidget *parent) : QWidget(parent) , d_ptr(new EventViewPrivate()) { QByteArray cname = metaObject()->className(); cname.replace(':', '_'); d_ptr->identifier = cname + '_' + KRandom::randomString(8).toLatin1(); //AKONADI_PORT review: the FocusLineEdit in the editor emits focusReceivedSignal(), //which triggered finishTypeAhead. But the global focus widget in QApplication is //changed later, thus subsequent keyevents still went to this view, triggering another //editor, for each keypress. //Thus, listen to the global focusChanged() signal (seen in Qt 4.6-stable-patched 20091112 -Frank) connect(qobject_cast<QApplication *>(QApplication::instance()), &QApplication::focusChanged, this, &EventView::focusChanged); d_ptr->setUpModels(); } EventView::~EventView() { delete d_ptr; } void EventView::defaultAction(const Akonadi::Item &aitem) { qCDebug(CALENDARVIEW_LOG); const Incidence::Ptr incidence = CalendarSupport::incidence(aitem); if (!incidence) { return; } qCDebug(CALENDARVIEW_LOG) << " type:" << int(incidence->type()); if (incidence->isReadOnly()) { Q_EMIT showIncidenceSignal(aitem); } else { Q_EMIT editIncidenceSignal(aitem); } } void EventView::setHolidayRegions(const QStringList ®ions) { Q_D(EventView); qDeleteAll(d->mHolidayRegions); d->mHolidayRegions.clear(); for (const QString ®ionStr : regions) { KHolidays::HolidayRegion *region = new KHolidays::HolidayRegion(regionStr); if (region->isValid()) { d->mHolidayRegions.append(region); } else { delete region; } } } int EventView::showMoveRecurDialog(const Incidence::Ptr &inc, const QDate &date) { QDateTime dateTime(date, {}, Qt::LocalTime); int availableOccurrences = KCalUtils::RecurrenceActions::availableOccurrences(inc, dateTime); const QString caption = i18nc("@title:window", "Changing Recurring Item"); KGuiItem itemFuture(i18n("Also &Future Items")); KGuiItem itemSelected(i18n("Only &This Item")); KGuiItem itemAll(i18n("&All Occurrences")); switch (availableOccurrences) { case KCalUtils::RecurrenceActions::NoOccurrence: return KCalUtils::RecurrenceActions::NoOccurrence; case KCalUtils::RecurrenceActions::SelectedOccurrence: return KCalUtils::RecurrenceActions::SelectedOccurrence; case KCalUtils::RecurrenceActions::AllOccurrences: Q_ASSERT(availableOccurrences & KCalUtils::RecurrenceActions::SelectedOccurrence); // if there are all kinds of ooccurrences (i.e. past present and future) the user might // want the option only apply to current and future occurrences, leaving the past ones // provide a third choice for that ("Also future") if (availableOccurrences == KCalUtils::RecurrenceActions::AllOccurrences) { const QString message = i18n("The item you are trying to change is a recurring item. " "Should the changes be applied only to this single occurrence, " "also to future items, or to all items in the recurrence?"); return KCalUtils::RecurrenceActions::questionSelectedFutureAllCancel( message, caption, itemSelected, itemFuture, itemAll, this); } Q_FALLTHROUGH(); default: Q_ASSERT(availableOccurrences & KCalUtils::RecurrenceActions::SelectedOccurrence); // selected occurrence and either past or future occurrences const QString message = i18n("The item you are trying to change is a recurring item. " "Should the changes be applied only to this single occurrence " "or to all items in the recurrence?"); return KCalUtils::RecurrenceActions::questionSelectedAllCancel( message, caption, itemSelected, itemAll, this); break; } return KCalUtils::RecurrenceActions::NoOccurrence; } void EventView::setCalendar(const Akonadi::ETMCalendar::Ptr &calendar) { Q_D(EventView); if (d->calendar != calendar) { if (d->calendar) { disconnect(d->calendar.data()); } d->calendar = calendar; if (calendar) { if (d->collectionSelectionModel) { d->collectionSelectionModel->setSourceModel(calendar->model()); } connect(calendar.data(), &ETMCalendar::collectionChanged, this, &EventView::onCollectionChanged); } } } Akonadi::ETMCalendar::Ptr EventView::calendar() const { Q_D(const EventView); return d->calendar; } void EventView::setPreferences(const PrefsPtr &preferences) { Q_D(EventView); if (d->mPrefs != preferences) { if (preferences) { d->mPrefs = preferences; } else { d->mPrefs = PrefsPtr(new Prefs()); } updateConfig(); } } void EventView::setKCalPreferences(const KCalPrefsPtr &preferences) { Q_D(EventView); if (d->mKCalPrefs != preferences) { if (preferences) { d->mKCalPrefs = preferences; } else { d->mKCalPrefs = KCalPrefsPtr(new CalendarSupport::KCalPrefs()); } updateConfig(); } } PrefsPtr EventView::preferences() const { Q_D(const EventView); return d->mPrefs; } KCalPrefsPtr EventView::kcalPreferences() const { Q_D(const EventView); return d->mKCalPrefs; } void EventView::dayPassed(const QDate &) { updateView(); } void EventView::setIncidenceChanger(Akonadi::IncidenceChanger *changer) { Q_D(EventView); d->mChanger = changer; } void EventView::flushView() { } EventView *EventView::viewAt(const QPoint &) { return this; } void EventView::updateConfig() { } QDateTime EventView::selectionStart() const { return QDateTime(); } QDateTime EventView::selectionEnd() const { return QDateTime(); } bool EventView::dateRangeSelectionEnabled() const { Q_D(const EventView); return d->mDateRangeSelectionEnabled; } void EventView::setDateRangeSelectionEnabled(bool enable) { Q_D(EventView); d->mDateRangeSelectionEnabled = enable; } bool EventView::supportsZoom() const { return false; } bool EventView::hasConfigurationDialog() const { return false; } void EventView::setDateRange(const QDateTime &start, const QDateTime &end, const QDate &preferredMonth) { Q_D(EventView); d->startDateTime = start; d->endDateTime = end; showDates(start.date(), end.date(), preferredMonth); const QPair<QDateTime, QDateTime> adjusted = actualDateRange(start, end, preferredMonth); d->actualStartDateTime = adjusted.first; d->actualEndDateTime = adjusted.second; } QDateTime EventView::startDateTime() const { Q_D(const EventView); return d->startDateTime; } QDateTime EventView::endDateTime() const { Q_D(const EventView); return d->endDateTime; } QDateTime EventView::actualStartDateTime() const { Q_D(const EventView); return d->actualStartDateTime; } QDateTime EventView::actualEndDateTime() const { Q_D(const EventView); return d->actualEndDateTime; } void EventView::showConfigurationDialog(QWidget *) { } bool EventView::processKeyEvent(QKeyEvent *ke) { Q_D(EventView); // If Return is pressed bring up an editor for the current selected time span. if (ke->key() == Qt::Key_Return) { if (ke->type() == QEvent::KeyPress) { d->mReturnPressed = true; } else if (ke->type() == QEvent::KeyRelease) { if (d->mReturnPressed) { Q_EMIT newEventSignal(); d->mReturnPressed = false; return true; } else { d->mReturnPressed = false; } } } // Ignore all input that does not produce any output if (ke->text().isEmpty() || (ke->modifiers() & Qt::ControlModifier)) { return false; } if (ke->type() == QEvent::KeyPress) { switch (ke->key()) { case Qt::Key_Escape: case Qt::Key_Return: case Qt::Key_Enter: case Qt::Key_Tab: case Qt::Key_Backtab: case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_Backspace: case Qt::Key_Delete: case Qt::Key_PageUp: case Qt::Key_PageDown: case Qt::Key_Home: case Qt::Key_End: case Qt::Key_Control: case Qt::Key_Meta: case Qt::Key_Alt: break; default: d->mTypeAheadEvents.append( new QKeyEvent(ke->type(), ke->key(), ke->modifiers(), ke->text(), ke->isAutoRepeat(), static_cast<ushort>(ke->count()))); if (!d->mTypeAhead) { d->mTypeAhead = true; Q_EMIT newEventSignal(); } return true; } } return false; } void EventView::setTypeAheadReceiver(QObject *o) { Q_D(EventView); d->mTypeAheadReceiver = o; } void EventView::focusChanged(QWidget *, QWidget *now) { Q_D(EventView); if (d->mTypeAhead && now && now == d->mTypeAheadReceiver) { d->finishTypeAhead(); } } CalendarSupport::CollectionSelection *EventView::collectionSelection() const { Q_D(const EventView); return d->customCollectionSelection ? d->customCollectionSelection : globalCollectionSelection(); } void EventView::setCustomCollectionSelectionProxyModel(KCheckableProxyModel *model) { Q_D(EventView); if (d->collectionSelectionModel == model) { return; } delete d->collectionSelectionModel; d->collectionSelectionModel = model; d->setUpModels(); } KCheckableProxyModel *EventView::customCollectionSelectionProxyModel() const { Q_D(const EventView); return d->collectionSelectionModel; } KCheckableProxyModel *EventView::takeCustomCollectionSelectionProxyModel() { Q_D(EventView); KCheckableProxyModel *m = d->collectionSelectionModel; d->collectionSelectionModel = nullptr; d->setUpModels(); return m; } CalendarSupport::CollectionSelection *EventView::customCollectionSelection() const { Q_D(const EventView); return d->customCollectionSelection; } void EventView::clearSelection() { } bool EventView::eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const { Q_UNUSED(startDt); Q_UNUSED(endDt); Q_UNUSED(allDay); return false; } Akonadi::IncidenceChanger *EventView::changer() const { Q_D(const EventView); return d->mChanger; } void EventView::doRestoreConfig(const KConfigGroup &) { } void EventView::doSaveConfig(KConfigGroup &) { } QPair<QDateTime, QDateTime> EventView::actualDateRange(const QDateTime &start, const QDateTime &end, const QDate &preferredMonth) const { Q_UNUSED(preferredMonth); return qMakePair(start, end); } /* void EventView::incidencesAdded( const Akonadi::Item::List & ) { } void EventView::incidencesAboutToBeRemoved( const Akonadi::Item::List & ) { } void EventView::incidencesChanged( const Akonadi::Item::List & ) { } */ void EventView::handleBackendError(const QString &errorString) { qCCritical(CALENDARVIEW_LOG) << errorString; } void EventView::calendarReset() { } CalendarSupport::CollectionSelection *EventView::globalCollectionSelection() { return EventViewPrivate::sGlobalCollectionSelection; } QByteArray EventView::identifier() const { Q_D(const EventView); return d->identifier; } void EventView::setIdentifier(const QByteArray &identifier) { Q_D(EventView); d->identifier = identifier; } void EventView::setChanges(Changes changes) { Q_D(EventView); if (d->mChanges == NothingChanged) { QMetaObject::invokeMethod(this, &EventView::updateView, Qt::QueuedConnection); } d->mChanges = changes; } EventView::Changes EventView::changes() const { Q_D(const EventView); return d->mChanges; } void EventView::restoreConfig(const KConfigGroup &configGroup) { Q_D(EventView); const bool useCustom = configGroup.readEntry("UseCustomCollectionSelection", false); if (!d->collectionSelectionModel && !useCustom) { delete d->collectionSelectionModel; d->collectionSelectionModel = nullptr; d->setUpModels(); } else if (useCustom) { if (!d->collectionSelectionModel) { // Sort the calendar model on calendar name QSortFilterProxyModel *sortProxy = new QSortFilterProxyModel(this); sortProxy->setDynamicSortFilter(true); sortProxy->setSortCaseSensitivity(Qt::CaseInsensitive); if (d->calendar) { sortProxy->setSourceModel(d->calendar->entityTreeModel()); } // Only show the first column. KRearrangeColumnsProxyModel *columnFilterProxy = new KRearrangeColumnsProxyModel(this); columnFilterProxy->setSourceColumns( QVector<int>() << Akonadi::ETMCalendar::CollectionTitle); columnFilterProxy->setSourceModel(sortProxy); // Make the calendar model checkable. d->collectionSelectionModel = new KCheckableProxyModel(this); d->collectionSelectionModel->setSourceModel(columnFilterProxy); d->setUpModels(); } const KConfigGroup selectionGroup = configGroup.config()->group(configGroup.name() + QLatin1String("_selectionSetup")); KViewStateMaintainer<ETMViewStateSaver> maintainer(selectionGroup); maintainer.setSelectionModel(d->collectionSelectionModel->selectionModel()); maintainer.restoreState(); } doRestoreConfig(configGroup); } void EventView::saveConfig(KConfigGroup &configGroup) { Q_D(EventView); configGroup.writeEntry("UseCustomCollectionSelection", d->collectionSelectionModel != nullptr); if (d->collectionSelectionModel) { KConfigGroup selectionGroup = configGroup.config()->group(configGroup.name() + QLatin1String("_selectionSetup")); KViewStateMaintainer<ETMViewStateSaver> maintainer(selectionGroup); maintainer.setSelectionModel(d->collectionSelectionModel->selectionModel()); maintainer.saveState(); } doSaveConfig(configGroup); } void EventView::setCollectionId(Akonadi::Collection::Id id) { Q_D(EventView); if (d->mCollectionId != id) { d->mCollectionId = id; } } Akonadi::Collection::Id EventView::collectionId() const { Q_D(const EventView); return d->mCollectionId; } bool EventView::makesWholeDayBusy(const KCalendarCore::Incidence::Ptr &incidence) const { // Must be event // Must be all day // Must be marked busy (TRANSP: OPAQUE) // You must be attendee or organizer if (incidence->type() != KCalendarCore::Incidence::TypeEvent || !incidence->allDay()) { return false; } KCalendarCore::Event::Ptr ev = incidence.staticCast<KCalendarCore::Event>(); if (ev->transparency() != KCalendarCore::Event::Opaque) { return false; } // Last check: must be organizer or attendee: if (kcalPreferences()->thatIsMe(ev->organizer().email())) { return true; } KCalendarCore::Attendee::List attendees = ev->attendees(); KCalendarCore::Attendee::List::ConstIterator it; for (it = attendees.constBegin(); it != attendees.constEnd(); ++it) { if (kcalPreferences()->thatIsMe((*it).email())) { return true; } } return false; } /*static*/ QColor EventView::itemFrameColor(const QColor &color, bool selected) { if (color.isValid()) { return selected ? QColor(85 + color.red() * 2.0 / 3, 85 + color.green() * 2.0 / 3, 85 + color.blue() * 2.0 / 3) : color.darker(115); } else { return Qt::black; } } QString EventView::iconForItem(const Akonadi::Item &item) { QString iconName; Akonadi::Collection collection = item.parentCollection(); while (collection.parentCollection().isValid() && collection.parentCollection() != Akonadi::Collection::root()) { collection = calendar()->collection(collection.parentCollection().id()); } if (collection.isValid() && collection.hasAttribute<Akonadi::EntityDisplayAttribute>()) { iconName = collection.attribute<Akonadi::EntityDisplayAttribute>()->iconName(); } return iconName; } void EventView::onCollectionChanged(const Akonadi::Collection &collection, const QSet<QByteArray> &changedAttributes) { Q_UNUSED(collection); if (changedAttributes.contains("AccessRights")) { setChanges(changes() | EventViews::EventView::ResourcesChanged); updateView(); } } 07070100000022000081A40000000200000002000000015F0BF3C9000042A0000000000000000000000000000000000000003E00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/eventview.h/* Copyright (c) 1999 Preston Brown <pbrown@kde.org> Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_EVENTVIEW_H #define EVENTVIEWS_EVENTVIEW_H #include "eventviews_export.h" #include <Collection> #include <Item> #include <Akonadi/Calendar/ETMCalendar> #include <KCalendarCore/Incidence> #include <KCalendarCore/Todo> #include <QWidget> #include <QSet> #include <QByteArray> #include <QDate> namespace CalendarSupport { class CollectionSelection; class KCalPrefs; } namespace Akonadi { class IncidenceChanger; } class KCheckableProxyModel; class KConfigGroup; namespace EventViews { enum { BUSY_BACKGROUND_ALPHA = 70 }; class EventViewPrivate; class Prefs; typedef QSharedPointer<Prefs> PrefsPtr; typedef QSharedPointer<CalendarSupport::KCalPrefs> KCalPrefsPtr; /** EventView is the abstract base class from which all other calendar views for event data are derived. It provides methods for displaying appointments and events on one or more days. The actual number of days that a view actually supports is not defined by this abstract class; that is up to the classes that inherit from it. It also provides methods for updating the display, retrieving the currently selected event (or events), and the like. @short Abstract class from which all event views are derived. @author Preston Brown <pbrown@kde.org> @see KOListView, AgendaView, KOMonthView */ class EVENTVIEWS_EXPORT EventView : public QWidget { Q_OBJECT public: enum { // This value is passed to QColor's lighter(int factor) for selected events BRIGHTNESS_FACTOR = 110 }; enum ItemIcon { CalendarCustomIcon = 0, TaskIcon, JournalIcon, RecurringIcon, ReminderIcon, ReadOnlyIcon, ReplyIcon, AttendingIcon, TentativeIcon, OrganizerIcon, IconCount = 10 // Always keep at the end }; enum Change { NothingChanged = 0, IncidencesAdded = 1, IncidencesEdited = 2, IncidencesDeleted = 4, DatesChanged = 8, FilterChanged = 16, ResourcesChanged = 32, ZoomChanged = 64, ConfigChanged = 128 }; Q_DECLARE_FLAGS(Changes, Change) /** * Constructs a view. * @param cal is a pointer to the calendar object from which events * will be retrieved for display. * @param parent is the parent QWidget. */ explicit EventView(QWidget *parent = nullptr); /** * Destructor. Views will do view-specific cleanups here. */ ~EventView(); virtual void setCalendar(const Akonadi::ETMCalendar::Ptr &cal); /** Return calendar object of this view. TODO: replace with a version that returns a KCalendarCore::Calendar so it can be used in different environments. see agendaview for example calendar2(incidence) */ virtual Akonadi::ETMCalendar::Ptr calendar() const; /* update config is called after prefs are set. */ virtual void setPreferences(const PrefsPtr &preferences); Q_REQUIRED_RESULT PrefsPtr preferences() const; virtual void setKCalPreferences(const KCalPrefsPtr &preferences); Q_REQUIRED_RESULT KCalPrefsPtr kcalPreferences() const; /** @return a list of selected events. Most views can probably only select a single event at a time, but some may be able to select more than one. */ virtual Akonadi::Item::List selectedIncidences() const = 0; /** Returns a list of the dates of selected events. Most views can probably only select a single event at a time, but some may be able to select more than one. */ virtual KCalendarCore::DateList selectedIncidenceDates() const = 0; /** Returns the start of the selection, or an invalid QDateTime if there is no selection or the view doesn't support selecting cells. */ virtual QDateTime selectionStart() const; /** Returns the end of the selection, or an invalid QDateTime if there is no selection or the view doesn't support selecting cells. */ virtual QDateTime selectionEnd() const; /** Sets the default start/end date/time for new events. Return true if anything was changed */ virtual bool eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const; /** Returns whether or not date range selection is enabled. This setting only applies to views that actually supports selecting cells. @see selectionStart() @see selectionEnd() */ Q_REQUIRED_RESULT bool dateRangeSelectionEnabled() const; /** Enable or disable date range selection. @see dateRangeSelectionEnabled() */ void setDateRangeSelectionEnabled(bool enable); /** Returns the number of currently shown dates. A return value of 0 means no idea. */ virtual int currentDateCount() const = 0; /** * returns whether this view supports zoom. * Base implementation returns false. */ virtual bool supportsZoom() const; virtual bool hasConfigurationDialog() const; virtual void showConfigurationDialog(QWidget *parent); Q_REQUIRED_RESULT QByteArray identifier() const; void setIdentifier(const QByteArray &identifier); /** * reads the view configuration. View-specific configuration can be * restored via doRestoreConfig() * * @param configGroup the group to read settings from * @see doRestoreConfig() */ void restoreConfig(const KConfigGroup &configGroup); /** * writes out the view configuration. View-specific configuration can be * saved via doSaveConfig() * * @param configGroup the group to store settings in * @see doSaveConfig() */ void saveConfig(KConfigGroup &configGroup); /** Makes the eventview display only items of collection @p id. Useful for example in multi-agendaview (side-by-side) where each AgendaView displays only one collection. */ void setCollectionId(Akonadi::Collection::Id id); Q_REQUIRED_RESULT Akonadi::Collection::Id collectionId() const; //---------------------------------------------------------------------------- KCheckableProxyModel *takeCustomCollectionSelectionProxyModel(); KCheckableProxyModel *customCollectionSelectionProxyModel() const; void setCustomCollectionSelectionProxyModel(KCheckableProxyModel *model); CalendarSupport::CollectionSelection *customCollectionSelection() const; static CalendarSupport::CollectionSelection *globalCollectionSelection(); static void setGlobalCollectionSelection(CalendarSupport::CollectionSelection *selection); //---------------------------------------------------------------------------- /** * returns the view at the given widget coordinate. This is usually the view * itself, except for composite views, where a subview will be returned. * The default implementation returns @p this . */ virtual EventView *viewAt(const QPoint &p); /** * @param preferredMonth Used by month orientated views. Contains the * month to show when the week crosses months. It's a QDate instead * of uint so it can be easily fed to KCalendarSystem's functions. */ virtual void setDateRange(const QDateTime &start, const QDateTime &end, const QDate &preferredMonth = QDate()); Q_REQUIRED_RESULT QDateTime startDateTime() const; Q_REQUIRED_RESULT QDateTime endDateTime() const; Q_REQUIRED_RESULT QDateTime actualStartDateTime() const; Q_REQUIRED_RESULT QDateTime actualEndDateTime() const; Q_REQUIRED_RESULT int showMoveRecurDialog(const KCalendarCore::Incidence::Ptr &incidence, const QDate &date); /** Handles key events, opens the new event dialog when enter is pressed, activates type ahead. */ Q_REQUIRED_RESULT bool processKeyEvent(QKeyEvent *); /* * Sets the QObject that will receive key events that were made * while the new event dialog was still being created. */ void setTypeAheadReceiver(QObject *o); /** Returns the selection of collection to be used by this view (custom if set, or global otherwise). */ CalendarSupport::CollectionSelection *collectionSelection() const; /** Notifies the view that there are pending changes so a redraw is needed. @param needed if the update is needed or not. */ virtual void setChanges(Changes changes); /** Returns if there are pending changes and a redraw is needed. */ Q_REQUIRED_RESULT Changes changes() const; /** * Returns a variation of @p color that will be used for the border * of an agenda or month item. */ Q_REQUIRED_RESULT static QColor itemFrameColor(const QColor &color, bool selected); Q_REQUIRED_RESULT QString iconForItem(const Akonadi::Item &); public Q_SLOTS: /** Shows given incidences. Depending on the actual view it might not be possible to show all given events. @param incidenceList a list of incidences to show. @param date is the QDate on which the incidences are being shown. */ virtual void showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) = 0; /** Updates the current display to reflect changes that may have happened in the calendar since the last display refresh. */ virtual void updateView() = 0; virtual void dayPassed(const QDate &); /** Assign a new incidence change helper object. */ virtual void setIncidenceChanger(Akonadi::IncidenceChanger *changer); /** Write all unsaved data back to calendar store. */ virtual void flushView(); /** Re-reads the configuration and picks up relevant changes which are applicable to the view. */ virtual void updateConfig(); /** Clear selection. The incidenceSelected signal is not emitted. */ virtual void clearSelection(); void focusChanged(QWidget *, QWidget *); /** Perform the default action for an incidence, e.g. open the event editor, when double-clicking an event in the agenda view. */ void defaultAction(const Akonadi::Item &incidence); /** Set which holiday regions the user wants to use. @param regions a list of Holiday Regions strings. */ void setHolidayRegions(const QStringList ®ions); Q_SIGNALS: /** * when the view changes the dates that are selected in one way or * another, this signal is emitted. It should be connected back to * the KDateNavigator object so that it changes appropriately, * and any other objects that need to be aware that the list of * selected dates has changed. * @param datelist the new list of selected dates */ void datesSelected(const KCalendarCore::DateList &datelist); /** * Emitted when an event is moved using the mouse in an agenda * view (week / month). */ void shiftedEvent(const QDate &olddate, const QDate &newdate); void incidenceSelected(const Akonadi::Item &, const QDate); /** * instructs the receiver to show the incidence in read-only mode. */ void showIncidenceSignal(const Akonadi::Item &); /** * instructs the receiver to begin editing the incidence specified in * some manner. Doesn't make sense to connect to more than one * receiver. */ void editIncidenceSignal(const Akonadi::Item &); /** * instructs the receiver to delete the Incidence in some manner; some * possibilities include automatically, with a confirmation dialog * box, etc. Doesn't make sense to connect to more than one receiver. */ void deleteIncidenceSignal(const Akonadi::Item &); /** * instructs the receiver to cut the Incidence */ void cutIncidenceSignal(const Akonadi::Item &); /** * instructs the receiver to copy the incidence */ void copyIncidenceSignal(const Akonadi::Item &); /** * instructs the receiver to paste the incidence */ void pasteIncidenceSignal(); /** * instructs the receiver to toggle the alarms of the Incidence. */ void toggleAlarmSignal(const Akonadi::Item &); /** * instructs the receiver to toggle the completion state of the Incidence * (which must be a Todo type). */ void toggleTodoCompletedSignal(const Akonadi::Item &); /** * Copy the incidence to the specified resource. */ void copyIncidenceToResourceSignal(const Akonadi::Item &, const Akonadi::Collection &); /** * Move the incidence to the specified resource. */ void moveIncidenceToResourceSignal(const Akonadi::Item &, const Akonadi::Collection &); /** Dissociate from a recurring incidence the occurrence on the given * date to a new incidence or dissociate all occurrences from the * given date onwards. */ void dissociateOccurrencesSignal(const Akonadi::Item &, const QDate &); /** * instructs the receiver to create a new event in given collection. Doesn't make * sense to connect to more than one receiver. */ void newEventSignal(); /** * instructs the receiver to create a new event with the specified beginning * time. Doesn't make sense to connect to more than one receiver. */ void newEventSignal(const QDate &); /** * instructs the receiver to create a new event with the specified beginning * time. Doesn't make sense to connect to more than one receiver. */ void newEventSignal(const QDateTime &); /** * instructs the receiver to create a new event, with the specified * beginning end ending times. Doesn't make sense to connect to more * than one receiver. */ void newEventSignal(const QDateTime &, const QDateTime &); void newTodoSignal(const QDate &); void newSubTodoSignal(const Akonadi::Item &); void newJournalSignal(const QDate &); protected Q_SLOTS: virtual void calendarReset(); private: void onCollectionChanged(const Akonadi::Collection &, const QSet<QByteArray> &); protected: bool makesWholeDayBusy(const KCalendarCore::Incidence::Ptr &incidence) const; Akonadi::IncidenceChanger *changer() const; /** * reimplement to read view-specific settings. */ virtual void doRestoreConfig(const KConfigGroup &configGroup); /** * reimplement to write view-specific settings. */ virtual void doSaveConfig(KConfigGroup &configGroup); /** @deprecated */ virtual void showDates(const QDate &start, const QDate &end, const QDate &preferredMonth = QDate()) = 0; /** * from the requested date range (passed via setDateRange()), calculates the * adjusted date range actually displayed by the view, depending on the * view's supported range (e.g., a month view always displays one month) * The default implementation returns the range unmodified * * @param preferredMonth Used by month orientated views. Contains the * month to show when the week crosses months. It's a QDate instead of * uint so it can be easily fed to KCalendarSystem's functions. */ virtual QPair<QDateTime, QDateTime> actualDateRange( const QDateTime &start, const QDateTime &end, const QDate &preferredMonth = QDate()) const; /* virtual void incidencesAdded( const Akonadi::Item::List &incidences ); virtual void incidencesAboutToBeRemoved( const Akonadi::Item::List &incidences ); virtual void incidencesChanged( const Akonadi::Item::List &incidences ); */ virtual void handleBackendError(const QString &error); private: EventViewPrivate *const d_ptr; Q_DECLARE_PRIVATE(EventView) }; } #endif 07070100000023000081A40000000200000002000000015F0BF3C9000009FE000000000000000000000000000000000000004200000000eventviews-VERSIONgit.20200713T074025~752bb43/src/eventview_p.cpp/* Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com Author: Sergio Martins, sergio.martins@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "eventview_p.h" #include "prefs.h" #include <CalendarSupport/CollectionSelection> #include <CalendarSupport/KCalPrefs> #include <KCheckableProxyModel> #include <QApplication> using namespace EventViews; EventViewPrivate::EventViewPrivate() : calendar(nullptr) , customCollectionSelection(nullptr) , collectionSelectionModel(nullptr) , mReturnPressed(false) , mDateRangeSelectionEnabled(true) , mTypeAhead(false) , mTypeAheadReceiver(nullptr) , mPrefs(new Prefs()) , mKCalPrefs(new CalendarSupport::KCalPrefs()) , mChanger(nullptr) , mChanges(EventView::DatesChanged) , mCollectionId(-1) { } EventViewPrivate::~EventViewPrivate() { delete collectionSelectionModel; } void EventViewPrivate::finishTypeAhead() { if (mTypeAheadReceiver) { for (QEvent *e : qAsConst(mTypeAheadEvents)) { QApplication::sendEvent(mTypeAheadReceiver, e); } } qDeleteAll(mTypeAheadEvents); mTypeAheadEvents.clear(); mTypeAhead = false; } void EventViewPrivate::setUpModels() { delete customCollectionSelection; customCollectionSelection = nullptr; if (collectionSelectionModel) { customCollectionSelection = new CalendarSupport::CollectionSelection( collectionSelectionModel->selectionModel()); } } 07070100000024000081A40000000200000002000000015F0BF3C900000B14000000000000000000000000000000000000004000000000eventviews-VERSIONgit.20200713T074025~752bb43/src/eventview_p.h/* Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com Author: Sergio Martins, sergio.martins@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_EVENTVIEW_P_H #define EVENTVIEWS_EVENTVIEW_P_H #include "eventview.h" namespace KHolidays { class HolidayRegion; } class KCheckableProxyModel; #include <QDateTime> namespace EventViews { class EventViewPrivate { public: /// Methods EventViewPrivate(); ~EventViewPrivate(); /** This is called when the new event dialog is shown. It sends all events in mTypeAheadEvents to the receiver. */ void finishTypeAhead(); public: // virtual functions void setUpModels(); public: /// Members Akonadi::ETMCalendar::Ptr calendar; CalendarSupport::CollectionSelection *customCollectionSelection; KCheckableProxyModel *collectionSelectionModel; QByteArray identifier; QDateTime startDateTime; QDateTime endDateTime; QDateTime actualStartDateTime; QDateTime actualEndDateTime; /* When we receive a QEvent with a key_Return release * we will only show a new event dialog if we previously received a * key_Return press, otherwise a new event dialog appears when * you hit return in some yes/no dialog */ bool mReturnPressed; bool mDateRangeSelectionEnabled; bool mTypeAhead; QObject *mTypeAheadReceiver = nullptr; QList<QEvent *> mTypeAheadEvents; static CalendarSupport::CollectionSelection *sGlobalCollectionSelection; QList<KHolidays::HolidayRegion *> mHolidayRegions; PrefsPtr mPrefs; KCalPrefsPtr mKCalPrefs; Akonadi::IncidenceChanger *mChanger = nullptr; EventView::Changes mChanges; Akonadi::Collection::Id mCollectionId; }; } // EventViews #endif 07070100000025000081A40000000200000002000000015F0BF3C900008CB7000000000000000000000000000000000000004200000000eventviews-VERSIONgit.20200713T074025~752bb43/src/eventviews.kcfg<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE kcfg SYSTEM "http://www.kde.org/standards/kcfg/1.0/kcfg.dtd"> <kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" > <kcfgfile name="eventviewsrc"/> <!-- PREFERENCES DIALOG --> <!-- General Page: Personal Tab --> <group name="Personal Settings"> <entry type="Enum" name="DefaultEmailAttachMethod"> <label>Default Email Attachment Method</label> <whatsthis>The default way of attaching dropped emails to an event</whatsthis> <choices> <choice name="Ask"> <label>Always ask</label> </choice> <choice name="Link"> <label>Only attach link to message</label> </choice> <choice name="InlineFull"> <label>Attach complete message</label> </choice> <choice name="InlineBody"> <label>Attach message without attachments</label> </choice> </choices> <default>Ask</default> </entry> </group> <!-- General Page: Save Tab --> <group name="Save Settings"> <entry type="Bool" key="Html With Save"> <label>Export to HTML with every save</label> <whatsthis>Check this box to export the calendar to a HTML-file every time you save it. By default, this file will be called calendar.html and placed in the user home folder.</whatsthis> <default>false</default> </entry> <entry type="Bool" key="Auto Save"> <label>Enable automatic saving of manually opened calendar files</label> <whatsthis>Check this box to save your calendar file automatically when you exit KOrganizer without asking and periodically, as you work. This setting does not affect the automatic saving of the standard calendar, which is automatically saved after each change.</whatsthis> <default>true</default> </entry> <entry type="Int" key="Auto Save Interval"> <label>Save &interval in minutes</label> <whatsthis>Set the interval between automatic saving of calendar events in minutes here. This setting only applies to files that are opened manually. The standard KDE-wide calendar is automatically saved after each change.</whatsthis> <default>10</default> <min>1</min> </entry> <entry type="Bool" key="Confirm Deletes" name="Confirm"> <label>Confirm deletes</label> <whatsthis>Check this box to display a confirmation dialog when deleting items.</whatsthis> <default>true</default> </entry> <entry type="Enum" key="Destination" name="Destination"> <label>New Events, To-dos and Journal Entries Should</label> <choices> <choice name="standardDestination"> <label>Be added to the standard calendar</label> <whatsthis>Select this option to always record new events, to-dos and journal entries using the standard calendar.</whatsthis> </choice> <choice name="askDestination"> <label>Be asked which calendar to use</label> <whatsthis>Select this option to choose the calendar to be used to record the item each time you create a new event, to-do or journal entry. This choice is recommended if you intend to use the shared folders functionality of the Kolab server or have to manage multiple accounts using Kontact as a KDE Kolab client. </whatsthis> </choice> </choices> <default>askDestination</default> </entry> </group> <!-- General Page: System Tray Tab --> <group name="System Tray"> <entry type="Bool" key="ShowReminderDaemon"> <label>Show Reminder Daemon in the System Tray</label> <whatsthis>Check this box to show the KOrganizer reminder daemon in the system tray.</whatsthis> <default>true</default> </entry> </group> <!-- Time&Date Page: Regional Tab --> <group name="Time & Date"> <entry type="String" name="TimeZoneId"> <whatsthis>Select your timezone from the list of locations in this drop down box. If your city is not listed, select one which shares the same timezone. KOrganizer will automatically adjust for daylight savings.</whatsthis> </entry> <entry type="DateTime" name="DayBegins"> <label>Day begins at</label> <whatsthis>Enter the start time for events here. This time should be the earliest time that you use for events, as it will be displayed at the top.</whatsthis> <default>QDateTime(QDate(1752,1,1), QTime(7,0))</default> </entry> <entry type="DateTime" name="WorkingHoursStart"> <label>Daily starting hour</label> <whatsthis>Enter the start time for the working hours here. The working hours will be marked with color by KOrganizer.</whatsthis> <default>QDateTime(QDate(1752,1,1), QTime(8,0))</default> </entry> <entry type="DateTime" name="WorkingHoursEnd"> <label>Daily ending hour</label> <whatsthis>Enter the ending time for the working hours here. The working hours will be marked with color by KOrganizer.</whatsthis> <default>QDateTime(QDate(1752,1,1), QTime(17,0))</default> </entry> <entry type="Enum" name="WeekStartDay"> <label>Weekly start day:</label> <tooltip>Select the first day of each week</tooltip> <whatsthis>Select the first day of each week. Normally, you can ignore this since it is set in your locale.</whatsthis> <default>QLocale().firstDayOfWeek()-1</default> </entry> </group> <!-- Views Page: General Tab --> <group name="General View"> <entry type="Bool" key="Enable ToolTips"> <label>Enable tooltips for displaying summaries</label> <whatsthis>Check this box to display summary tooltips when hovering the mouse over an event or a to-do.</whatsthis> <default>true</default> </entry> <entry type="Bool" key="TodosUseCategoryColors"> <label>To-dos use category colors</label> <whatsthis>Check this box so that to-dos will use category colors and not colors specific to their due, due today or overdue state</whatsthis> <default>false</default> </entry> <entry type="Int" key="Next X Days"> <label>Next x days</label> <whatsthis>Select the number of "x" days to be displayed in the next days view. To access the next "x" days view, choose the "Next X Days" menu item from the "View" menu.</whatsthis> <tooltip>Show this many days at a time in the Next "x" days view</tooltip> <default>3</default> </entry> <entry type="Bool" key="Show Daily Recurrences" name="DailyRecur"> <label>Show items that recur daily</label> <whatsthis>Check this box to show the days containing daily recurring events in bold typeface in the Date Navigator, or uncheck it to give more prominence to other (non daily recurring) events.</whatsthis> <default>true</default> </entry> <entry type="Bool" key="Show Weekly Recurrences" name="WeeklyRecur"> <label>Show items that recur weekly</label> <whatsthis>Check this box to show the days containing weekly recurring events in bold typeface in the Date Navigator, or uncheck it to give more prominence to other (non weekly recurring) events.</whatsthis> <default>true</default> </entry> <entry type="Bool" key="Highlight To-dos" name="HighlightTodos"> <label>Show to-dos instead of events when in Todo View</label> <whatsthis>Check this box to show the days containing to-dos in bold typeface in the Date Navigator when in to-do view.</whatsthis> <default>true</default> </entry> <entry type="Bool" key="Highlight Journals" name="HighlightJournals"> <label>Show journals instead of events when in Journal View</label> <whatsthis>Check this box to show the days containing journals in bold typeface in the Date Navigator when in journal view.</whatsthis> <default>true</default> </entry> <entry type="Bool" key="Week Numbers Show Work Week" name="WeekNumbersShowWork"> <label>Week numbers select a work week when in work week mode</label> <whatsthis>Check this box to select a working week when clicking on the Date Navigator's week numbers, or uncheck it to choose the whole week.</whatsthis> <default>false</default> </entry> </group> <!-- Views Page: Agenda View Tab --> <group name="Agenda View"> <entry type="Int" key="Hour Size"> <label>Hour size</label> <whatsthis>Select the height of the hour rows in the agenda grid, in pixels. Increasing this value will make each row in the agenda grid taller.</whatsthis> <tooltip>Set the height (in pixels) for an hour in the agenda grid</tooltip> <default>10</default> <min>4</min> <max>30</max> </entry> <entry type="Bool" key="Show Icons in Agenda View" name="EnableAgendaItemIcons"> <label>Show icons in agenda view items</label> <whatsthis>Check this box to display icons (alarm, recursion, etc.) in agenda view items.</whatsthis> <tooltip>Display icons in agenda view items</tooltip> <default>true</default> </entry> <entry type="Bool" name="ShowTodosAgendaView"> <label>Show to-dos</label> <whatsthis>Check this box to display to-dos in the agenda view.</whatsthis> <tooltip>Display to-dos in the agenda view</tooltip> <default>true</default> </entry> <entry type="Bool" key="Show current-time line" name="MarcusBainsEnabled"> <label>Show current-time (Marcus Bains) line</label> <whatsthis>Check this box to display a line in the day or week view indicating the current-time line (Marcus Bains line).</whatsthis> <tooltip>Display the current-time indicator</tooltip> <default>true</default> </entry> <entry type="Bool" key="Current-time line shows seconds" name="MarcusBainsShowSeconds"> <label>Show seconds on the current-time (Marcus Bains) line</label> <whatsthis>Check this box if you want to show seconds on the current-time line.</whatsthis> <tooltip>Display seconds with the current-time indicator</tooltip> <default>false</default> </entry> <entry type="Bool" name="SelectionStartsEditor"> <label>Time range selection in agenda view starts event editor</label> <whatsthis>Check this box to start the event editor automatically when you select a time range in the daily and weekly view. To select a time range, drag the mouse from the start time to the end time of the event you are about to plan.</whatsthis> <tooltip>Enable automatic event editor with time range selection</tooltip> <default>false</default> </entry> <entry type="Enum" key="AgendaViewColors"> <label>Color Usage</label> <choices> <choice name="CategoryInsideResourceOutside"> <label>Category inside, calendar outside</label> <whatsthis>Select the "Category inside, calendar outside" option if you would like to draw calendar items in their associated category color, with the item's border drawn in the color of its calendar. Please use the Colors and Fonts configuration page for setting these colors.</whatsthis> <tooltip>Draw agenda items in their category color inside and calendar color for their border</tooltip> </choice> <choice name="ResourceInsideCategoryOutside"> <label>Calendar inside, category outside</label> <whatsthis>Select the "Calendar inside, category outside" option if you would like to draw calendar items in their associated calendar color, with the item's border drawn in the color of its category. Please use the Colors and Fonts configuration page for setting these colors.</whatsthis> <tooltip>Draw agenda items in their calendar color inside and category color for their border</tooltip> </choice> <choice name="CategoryOnly"> <label>Only category</label> <whatsthis>Select the "Only category" option if you would like to draw calendar items (both inside and border) in the color of their associated category. Please use the Colors and Fonts configuration page for setting these colors.</whatsthis> <tooltip>Draw agenda items using their category color for the inside and border</tooltip> </choice> <choice name="ResourceOnly"> <label>Only calendar</label> <whatsthis>Select the "Only calendar" option if you would like to draw calendar items (both inside and border) in the color of their calendar. Please use the Colors and Fonts configuration page for setting these colors.</whatsthis> <tooltip>Draw agenda items using their calendar color for the inside and border</tooltip> </choice> </choices> <default>CategoryInsideResourceOutside</default> </entry> <entry type="Bool" key="ColorBusyDaysEnabled" name="ColorBusyDaysEnabled"> <label>Color busy days with a different background color</label> <whatsthis>Check this box if you want agenda's background to be filled with a different color on days which have at least one all day event marked as busy. Also, you can change the background color used for this option on the Colors configuration page. Look for the "Busy days background color" setting.</whatsthis> <default>false</default> </entry> <entry type="Enum" name="AgendaViewCalendarDisplay"> <label>Multiple Calendar Display</label> <choices> <choice name="CalendarsMerged"> <label>Merge all calendars into one view</label> <whatsthis>Select the "Merge all calendars into one view" option if you would like all your calendars to be shown together in one agenda view.</whatsthis> <tooltip>Show all calendars merged together</tooltip> </choice> <choice name="CalendarsSideBySide"> <label>Show calendars side by side</label> <whatsthis>Select the "Show calendars side by side" option if you would like to see two calendars at once, in a side-by-side view.</whatsthis> <tooltip>Show two calendars side-by-side</tooltip> </choice> <choice name="AllCalendarViews"> <label>Switch between views with tabs</label> <whatsthis>Select "Switch between views with tabs" if you would like to alternate between calendars using the tab key.</whatsthis> <tooltip>Tab through calendars</tooltip> </choice> </choices> <default>CalendarsMerged</default> </entry> </group> <!-- Views Page: Month View Tab --> <group name="Month View"> <!-- <entry type="Bool" key="Enable Month-View Scrollbars" name="EnableMonthScroll"> <label>Enable scrollbars in month view cells</label> <whatsthis>Check this box to display scrollbars when clicking on a cell in the month view; they will only appear when needed though.</whatsthis> <default>false</default> </entry> --> <entry type="Bool" key="Show Icons in Month View" name="EnableMonthItemIcons"> <label>Show icons in month view items</label> <whatsthis>Check this box to display icons (alarm, recursion, etc.) in month view items.</whatsthis> <tooltip>Display icons in month view items</tooltip> <default>true</default> </entry> <entry type="Bool" key="Show time in Month View" name="ShowTimeInMonthView"> <label>Show time in month view items</label> <whatsthis>Check this box to display the time in month view items.</whatsthis> <tooltip>Display time in month view items</tooltip> <default>false</default> </entry> <entry type="Bool" name="ShowTodosMonthView"> <label>Show to-dos</label> <whatsthis>Check this box to display to-dos in the month view.</whatsthis> <tooltip>Display to-dos in the month view</tooltip> <default>true</default> </entry> <entry type="Bool" name="ShowJournalsMonthView"> <label>Show journals</label> <whatsthis>Check this box to display journals in the month view.</whatsthis> <tooltip>Display journals in the month view</tooltip> <default>true</default> </entry> <entry type="Bool" key="Full View Month"> <label>Month view uses full window</label> <whatsthis>Check this box to use the full KOrganizer window when displaying the month view. If this box is checked, you will gain some space for the monthly view, but other widgets, such as the date navigator, the item details and the calendars list, will not be displayed.</whatsthis> <default>false</default> </entry> <entry type="Enum" key="MonthViewColors"> <label>Color Usage</label> <choices> <choice name="MonthItemCategoryInsideResourceOutside"> <label>Category inside, calendar outside</label> <whatsthis>Select the "Category inside, calendar outside" option if you would like to draw calendar items in their associated category color, with the item's border drawn in the color of its calendar. Please use the Colors and Fonts configuration page for setting these colors.</whatsthis> <tooltip>Draw month items in their category color inside and calendar color for their border</tooltip> </choice> <choice name="MonthItemResourceInsideCategoryOutside"> <label>Calendar inside, category outside</label> <whatsthis>Select the "Calendar inside, category outside" option if you would like to draw calendar items in their associated calendar color, with the item's border drawn in the color of its category. Please use the Colors and Fonts configuration page for setting these colors.</whatsthis> <tooltip>Draw month items in their calendar color inside and category color for their border</tooltip> </choice> <choice name="MonthItemCategoryOnly"> <label>Only category</label> <whatsthis>Select the "Only category" option if you would like to draw calendar items (both inside and border) in the color of their associated category. Please use the Colors and Fonts configuration page for setting these colors.</whatsthis> <tooltip>Draw month items using their category color for the inside and border</tooltip> </choice> <choice name="MonthItemResourceOnly"> <label>Only calendar</label> <whatsthis>Select the "Only calendar" option if you would like to draw calendar items (both inside and border) in the color of their calendar. Please use the Colors and Fonts configuration page for setting these colors.</whatsthis> <tooltip>Draw month items using their calendar color for the inside and border</tooltip> </choice> </choices> <default>MonthItemCategoryInsideResourceOutside</default> </entry> <entry type="Bool" key="ColorMonthBusyDaysEnabled" name="ColorMonthBusyDaysEnabled"> <label>Color busy days with a different background color</label> <whatsthis>Check this box if you want month view's background to be filled with a different color on days which have at least one all day event marked as busy. Also, you can change the background color used for this option on the Colors configuration page. Look for the "Busy days background color" setting.</whatsthis> <default>false</default> </entry> </group> <!-- Views Page: Todo View Tab --> <group name="Todo View"> <entry type="Bool" key="Sort Completed Todos Separately"> <label>Always display completed to-dos at the bottom of the list</label> <whatsthis>Check this box if you want all completed to-dos to be always grouped at the bottom of the to-do list.</whatsthis> <default>true</default> </entry> <entry type="Bool" key="Full View Todo"> <label>To-do list view uses full window</label> <whatsthis>Check this box to use the full KOrganizer window when displaying the to-do list view. If this box is checked, you will gain some space for the to-do list view, but other widgets, such as the date navigator, the to-do details and the calendars list, will not be displayed.</whatsthis> <default>true</default> </entry> <entry type="Bool" key="Flat List Todo"> <label>Display to-dos in a flat list</label> <tooltip>Display to-dos as a flat list instead of a hierarchical tree</tooltip> <whatsthis>Check this box if you want to see all your to-dos in a flat list instead of being arranged in a hierarchical tree.</whatsthis> <default>false</default> </entry> <entry type="Bool" key="Record Todos In Journals"> <label>Record completed to-dos in journal entries</label> <whatsthis>Check this box to record the completion of a to-do in a new entry of your journal automatically.</whatsthis> <default>false</default> </entry> </group> <!-- Colors and Fonts Page: Colors Tab --> <group name="Colors"> <entry type="Color" key="Holiday Color"> <label>Holiday color</label> <whatsthis>Select the holiday color here. The holiday color will be used for the holiday name in the month view and the holiday number in the date navigator.</whatsthis> <default>255, 100, 100</default> </entry> <entry type="Color" key="AgendaBackgroundColor" name="AgendaBgColor"> <label>Agenda view background color</label> <whatsthis>Select the agenda view background color here.</whatsthis> <default>255, 255, 255</default> </entry> <entry type="Color" key="ViewBackgroundBusyColor" name="ViewBgBusyColor"> <label>Agenda/Month view background busy color</label> <whatsthis>When you have an all day event marked as busy, you can have a different agenda or month view background color for that day. Select the color here.</whatsthis> <default>136, 255, 219</default> </entry> <entry type="Color" key="Agenda MarcusBainsLine Line Color"> <label>Agenda view current-time line color</label> <whatsthis>Select a color to use for the current-time (Marcus Bains) line.</whatsthis> <tooltip>Use this color for the Agenda View current-time (Marcus Bains) line</tooltip> <default>255,0,0</default> </entry> <entry type="Color" key="WorkingHoursColor"> <label>Working hours color</label> <whatsthis>Select the working hours color for the agenda view here.</whatsthis> <default>255, 235, 154</default> </entry> <entry type="Color" key="Todo due today Color" name="TodoDueTodayColor"> <label>To-do due today color</label> <whatsthis>Select the to-do due today color here.</whatsthis> <default>255, 200, 50</default> </entry> <entry type="Color" key="Todo overdue Color" name="TodoOverdueColor"> <label>To-do overdue color</label> <whatsthis>Select the to-do overdue color here.</whatsthis> <default>255, 100, 100</default> </entry> </group> <!-- Colors and Fonts Page: Fonts Tab --> <group name="Fonts"> <entry key="Agenda TimeLabels Font" type="Font"> <label>Time bar</label> <whatsthis>Press this button to configure the time bar font. The time bar is the widget that shows the hours in the agenda view. This button will open the "Select Font" dialog, allowing you to choose the hour font for the time bar.</whatsthis> </entry> <entry type="Font" key="MonthView Font"> <label>Month view</label> <whatsthis>Press this button to configure the month view font. This button will open the "Select Font" dialog, allowing you to choose the font for the items in the month view.</whatsthis> </entry> <entry type="Font" key="AgendaView Font"> <label>Agenda view</label> <whatsthis>Press this button to configure the agenda view font. This button will open the "Select Font" dialog, allowing you to choose the font for the events in the agenda view.</whatsthis> </entry> <entry key="Agenda MarcusBainsLine Font" type="Font"> <label>Current-time line</label> <whatsthis>Press this button to configure the current-time line font. This button will open the "Select Font" dialog, allowing you to choose the font for the current-time line in the agenda view.</whatsthis> </entry> </group> <!-- Plugins Page --> <group name="KOrganizer Plugins"> <entry type="StringList" name="SelectedPlugins"> <default></default> </entry> </group> <!-- Custom Pages Page --> <!-- Options currently not visible in the GUI --> <group name="Hidden Options"> <entry type="Bool" key="Quick Todo" name="EnableQuickTodo"> <default>true</default> </entry> <entry type="Bool" key="Todo Quick Search" name="EnableTodoQuickSearch"> <default>true</default> </entry> <entry type="Enum" name="DefaultTodoAttachMethod"> <label>Default todo attachment method</label> <whatsthis>The default way of attaching dropped emails to a task</whatsthis> <choices> <choice name="TodoAttachAsk"> <label>Always ask</label> </choice> <choice name="TodoAttachLink"> <label>Only attach link to message</label> </choice> <choice name="TodoAttachInlineFull"> <label>Attach complete message</label> </choice> </choices> <default>TodoAttachInlineFull</default> </entry> <entry type="Bool" key="CompactDialogs"> <default>false</default> </entry> <entry key="ShowMenuBar" type="Bool"> <default>true</default> <!-- label and whatsthis are already provided by KStandardAction::showMenubar --> <label></label> <whatsthis></whatsthis> </entry> </group> <!-- INTERNAL SETTINGS: Not for users to change --> <group name="Internal Settings"> <entry type="Int" key="DefaultResourceColorSeed"> <default>0</default> </entry> <entry type="StringList" key="DefaultResourceColors"> <default>#c1d4e7,#d0e7c1,#e3e7c1,#e7c1e6,#a1b1c1</default> </entry> <entry type="Bool" key="AssignDefaultResourceColors"> <default>true</default> </entry> <entry key="Decorations At Month View Top" type="StringList"> </entry> <entry key="Decorations At Agenda View Top" type="StringList"> </entry> <entry key="Decorations At Agenda View Bottom" type="StringList"> </entry> </group> <!-- Theming settings (not implemented yet) --> <group name="Theming"> <!-- BEGIN: These entries are the same for the agenda and month views, please keep them in sync --> <entry key="Agenda Grid Background Color" type="Color"> <label>Agenda view background color</label> <whatsthis>Select the agenda view background color here.</whatsthis> <default>255, 255, 255</default> </entry> <entry key="Agenda Grid Background Image" type="String"> </entry> <entry key="Agenda Grid Highlight Color" type="Color"> <label>Highlight color</label> <whatsthis>Select the highlight color here. The highlight color will be used for marking the currently selected area in your agenda and in the date navigator.</whatsthis> <default>100, 100, 255</default> </entry> <entry key="Agenda Grid WorkHours Background Color" type="Color"> <label>Working hours color</label> <whatsthis>Select the working hours background color for the agenda view here.</whatsthis> <default>225, 225, 255</default> </entry> <entry key="Agenda Grid WorkHours Background Image" type="String"> </entry> <!-- END: These entries are the same for the agenda and month views, please keep them in sync --> <!-- BEGIN: These entries are the same for the agenda and month views, please keep them in sync --> <entry key="Agenda CalendarItems Background Color" type="Color"> <default>255, 100, 100</default><!-- FIXME --> </entry> <entry key="Agenda CalendarItems Background Image" type="String"> </entry> <entry key="Agenda CalendarItems Font" type="Font"> </entry> <entry key="Agenda CalendarItems Frame Color" type="Color"> <default>255, 100, 100</default><!-- FIXME --> </entry> <entry key="Agenda CalendarItems Icon" type="String"> </entry> <entry key="Agenda CalendarItems Events Background Image" type="String"> </entry> <entry key="Agenda CalendarItems Events Font" type="Font"> </entry> <entry key="Agenda CalendarItems Events Frame Color" type="Color"> <default>255, 100, 100</default><!-- FIXME --> </entry> <entry key="Agenda CalendarItems Events Icon" type="String"> </entry> <entry key="Agenda CalendarItems ToDos Background Color" type="Color"> <default>255, 100, 100</default><!-- FIXME --> </entry> <entry key="Agenda CalendarItems ToDos Background Image" type="String"> </entry> <entry key="Agenda CalendarItems ToDos Font" type="Font"> </entry> <entry key="Agenda CalendarItems ToDos Frame Color" type="Color"> <default>255, 100, 100</default><!-- FIXME --> </entry> <entry key="Agenda CalendarItems ToDos Icon" type="String"> </entry> <entry key="Agenda CalendarItems ToDos DueToday Background Color" type="Color"> <label>To-do due today color</label> <whatsthis>Select the to-do due today color here.</whatsthis> <default>255, 200, 50</default> </entry> <entry key="Agenda CalendarItems ToDos DueToday Background Image" type="String"> </entry> <entry key="Agenda CalendarItems ToDos DueToday Font" type="Font"> </entry> <entry key="Agenda CalendarItems ToDos DueToday Frame Color" type="Color"> </entry> <entry key="Agenda CalendarItems ToDos DueToday Icon" type="String"> </entry> <entry key="Agenda CalendarItems ToDos Overdue Background Color" type="Color"> <label>To-do overdue color</label> <whatsthis>Select the to-do overdue color here.</whatsthis> <default>255, 100, 100</default> </entry> <entry key="Agenda CalendarItems ToDos Overdue Background Image" type="String"> </entry> <entry key="Agenda CalendarItems ToDos Overdue Font" type="Font"> </entry> <entry key="Agenda CalendarItems ToDos Overdue Frame Color" type="Color"> </entry> <entry key="Agenda CalendarItems ToDos Overdue Icon" type="String"> </entry> <!-- TODO: categories, resources --> <entry key="Agenda Holidays Background Color" type="Color"> <label>Holiday color</label> <whatsthis>Select the holiday color here. The holiday color will be used for the holiday name in the month view and the holiday number in the date navigator.</whatsthis> <default>255, 100, 100</default> </entry> <entry key="Agenda Holidays Background Image" type="String"> </entry> <!-- END: These entries are the same for the agenda and month views, please keep them in sync --> </group> <group name="Theme/Month view"> <!-- BEGIN: These entries are the same for the Month and month views, please keep them in sync --> <entry key="Month Grid Background Color" type="Color"> <label>Month view background color</label> <whatsthis>Select the Month view background color here.</whatsthis> <default>255, 255, 255</default> </entry> <entry key="Month Grid Background Image" type="String"> </entry> <entry key="Month Grid Highlight Color" type="Color"> <label>Highlight color</label> <whatsthis>Select the highlight color here. The highlight color will be used for marking the currently selected area in your Month and in the date navigator.</whatsthis> <default>100, 100, 255</default> </entry> <entry key="Month Grid WorkHours Background Color" type="Color"> <label>Working hours color</label> <whatsthis>Select the working hours background color for the Month view here.</whatsthis> <default>225, 225, 255</default> </entry> <entry key="Month Grid WorkHours Background Image" type="String"> </entry> <!-- END: These entries are the same for the Month and month views, please keep them in sync --> <!-- BEGIN: These entries are the same for the Month and month views, please keep them in sync --> <entry key="Month CalendarItems Background Color" type="Color"> <default>255, 100, 100</default><!-- FIXME --> </entry> <entry key="Month CalendarItems Background Image" type="String"> </entry> <entry key="Month CalendarItems Font" type="Font"> </entry> <entry key="Month CalendarItems Frame Color" type="Color"> <default>255, 100, 100</default><!-- FIXME --> </entry> <entry key="Month CalendarItems Icon" type="String"> </entry> <entry key="Month CalendarItems Events Background Color" type="Color"> <label>Default event color</label> <whatsthis>Select the default event color here. The default event color will be used for events categories in your Month. Note that you can specify a separate color for each event category below.</whatsthis> <default>151, 235, 121</default> </entry> <entry key="Month CalendarItems Events Background Image" type="String"> </entry> <entry key="Month CalendarItems Events Font" type="Font"> </entry> <entry key="Month CalendarItems Events Frame Color" type="Color"> <default>255, 100, 100</default><!-- FIXME --> </entry> <entry key="Month CalendarItems Events Icon" type="String"> </entry> <entry key="Month CalendarItems ToDos Background Color" type="Color"> <default>255, 100, 100</default><!-- FIXME --> </entry> <entry key="Month CalendarItems ToDos Background Image" type="String"> </entry> <entry key="Month CalendarItems ToDos Font" type="Font"> </entry> <entry key="Month CalendarItems ToDos Frame Color" type="Color"> <default>255, 100, 100</default><!-- FIXME --> </entry> <entry key="Month CalendarItems ToDos Icon" type="String"> </entry> <entry key="Month CalendarItems ToDos DueTodayBackground Color" type="Color"> <label>To-do due today color</label> <whatsthis>Select the to-do due today color here.</whatsthis> <default>255, 200, 50</default> </entry> <entry key="Month CalendarItems ToDos DueToday Background Image" type="String"> </entry> <entry key="Month CalendarItems ToDos DueToday Font" type="Font"> </entry> <entry key="Month CalendarItems ToDos DueToday Frame Color" type="Color"> </entry> <entry key="Month CalendarItems ToDos DueToday Icon" type="String"> </entry> <entry key="Month CalendarItems ToDos Overdue Background Color" type="Color"> <label>To-do overdue color</label> <whatsthis>Select the to-do overdue color here.</whatsthis> <default>255, 100, 100</default> </entry> <entry key="Month CalendarItems ToDos Overdue Background Image" type="String"> </entry> <entry key="Month CalendarItems ToDos Overdue Font" type="Font"> </entry> <entry key="Month CalendarItems ToDos Overdue Frame Color" type="Color"> </entry> <entry key="Month CalendarItems ToDos Overdue Icon" type="String"> </entry> <!-- TODO: categories, resources --> <entry key="Month Holidays Background Color" type="Color"> <label>Holiday color</label> <whatsthis>Select the holiday color here. The holiday color will be used for the holiday name in the month view and the holiday number in the date navigator.</whatsthis> <default>255, 100, 100</default> </entry> <entry key="Month Holidays Background Image" type="String"> </entry> <!-- END: These entries are the same for the Month and month views, please keep them in sync --> <entry type="Bool" key="Use System Color"> <label>Use system color</label> <whatsthis>Use system color. If enabled will ignore all other color settings</whatsthis> <default>true</default> </entry> </group> </kcfg> 07070100000026000081A40000000200000002000000015F0BF3C900000EA7000000000000000000000000000000000000003D00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/helper.cpp/* Copyright (C) 2005 Reinhold Kainhofer <reinhold@kainhofer.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "helper.h" #include "prefs.h" #include "calendarview_debug.h" #include <Collection> #include <Item> #include <AkonadiCore/CollectionColorAttribute> #include <AkonadiCore/CollectionModifyJob> #include <QIcon> #include <QPixmap> bool EventViews::isColorDark(const QColor &c) { double luminance = (c.red() * 0.299) + (c.green() * 0.587) + (c.blue() * 0.114); return (luminance < 128.0) ? true : false; } QColor EventViews::getTextColor(const QColor &c) { return (!isColorDark(c)) ? QColor(0, 0, 0) : QColor(255, 255, 255); } void EventViews::setResourceColor(const Akonadi::Collection &coll, const QColor &color, const PrefsPtr &preferences) { if (!coll.isValid()) { return; } const QString id = QString::number(coll.id()); // Save the color in akonadi (so the resource can even save it server-side) Akonadi::Collection collection = coll; Akonadi::CollectionColorAttribute *colorAttr = collection.attribute<Akonadi::CollectionColorAttribute>(Akonadi::Collection::AddIfMissing); colorAttr->setColor(color); Akonadi::CollectionModifyJob *job = new Akonadi::CollectionModifyJob(collection, nullptr); QObject::connect(job, &Akonadi::CollectionModifyJob::result, [=]() { if (job->error()) { qCWarning(CALENDARVIEW_LOG) << "Failed to set CollectionColorAttribute:" << job->errorString(); } }); // Also save the color in eventviewsrc (mostly historical) preferences->setResourceColor(id, color); } QColor EventViews::resourceColor(const Akonadi::Collection &coll, const PrefsPtr &preferences) { if (!coll.isValid()) { return QColor(); } const QString id = QString::number(coll.id()); // Color stored in eventviewsrc (and in memory) QColor color = preferences->resourceColorKnown(id); if (color.isValid()) { return color; } // Color stored in akonadi if (coll.hasAttribute<Akonadi::CollectionColorAttribute>()) { const Akonadi::CollectionColorAttribute *colorAttr = coll.attribute<Akonadi::CollectionColorAttribute>(); if (colorAttr && colorAttr->color().isValid()) { return colorAttr->color(); } } // Generate new color and store it in eventsviewsrc (and in memory) return preferences->resourceColor(id); } QColor EventViews::resourceColor(const Akonadi::Item &item, const PrefsPtr &preferences) { if (!item.isValid()) { return QColor(); } return resourceColor(item.parentCollection(), preferences); } int EventViews::yearDiff(const QDate &start, const QDate &end) { return end.year() - start.year(); } QPixmap EventViews::cachedSmallIcon(const QString &name) { return QIcon::fromTheme(name).pixmap(16, 16); } 07070100000027000081A40000000200000002000000015F0BF3C900000ED0000000000000000000000000000000000000003B00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/helper.h/* Copyright (C) 2005 Reinhold Kainhofer <reinhold@kainhofer.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_HELPER_H #define EVENTVIEWS_HELPER_H #include "eventviews_export.h" #include <QColor> #include <QSharedPointer> namespace Akonadi { class Collection; class Item; } class QPixmap; class QDate; // Provides static methods that are useful to all views. namespace EventViews { class Prefs; typedef QSharedPointer<Prefs> PrefsPtr; /** Returns a nice QColor for text, give the input color &c. */ Q_REQUIRED_RESULT QColor getTextColor(const QColor &c); /** * Determines if the @p color is "dark" or "light" by looking at its luminance. * idea taken from: * https://stackoverflow.com/questions/9780632/how-do-i-determine-if-a-color-is-closer-to-white-or-black * * @return true if the specified color is closer to black than white. */ Q_REQUIRED_RESULT bool isColorDark(const QColor &color); /** This method returns the proper resource / subresource color for the view. If a value is stored in the preferences, we use it, else we try to find a CollectionColorAttribute in the collection. If everything else fails, a random color can be set. It is preferred to use this function instead of the EventViews::Prefs::resourceColor function. @return The resource color for the incidence. If the incidence belongs to a subresource, the color for the subresource is returned (if set). @param calendar the calendar for which the resource color should be obtained @param incidence the incidence for which the color is needed (to determine which subresource needs to be used) */ Q_REQUIRED_RESULT EVENTVIEWS_EXPORT QColor resourceColor(const Akonadi::Item &incidence, const PrefsPtr &preferences); Q_REQUIRED_RESULT EVENTVIEWS_EXPORT QColor resourceColor(const Akonadi::Collection &collection, const PrefsPtr &preferences); /** This method sets the resource color as an Akonadi collection attribute and in the local preferences. It is preferred to use this instead of the EventViews::Prefs::setResourceColor function. @param collection the collection for which the resource color should be stored @param color the color to stored @param preferences a pointer to the EventViews::Prefs to use */ EVENTVIEWS_EXPORT void setResourceColor(const Akonadi::Collection &collection, const QColor &color, const PrefsPtr &preferences); /** Returns the number of years between the @p start QDate and the @p end QDate (i.e. the difference in the year number of both dates) */ Q_REQUIRED_RESULT int yearDiff(const QDate &start, const QDate &end); /** Equivalent to SmallIcon( name ), but uses QPixmapCache. KIconLoader already uses a cache, but it's 20x slower on my tests. @return A new pixmap if it isn't yet in cache, otherwise returns the cached one. */ Q_REQUIRED_RESULT QPixmap cachedSmallIcon(const QString &name); } #endif 07070100000028000041ED0000000200000002000000025F0BF3C900000000000000000000000000000000000000000000003A00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/journal07070100000029000081A40000000200000002000000015F0BF3C900002A3A000000000000000000000000000000000000004B00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/journal/journalframe.cpp/* This file is part of KOrganizer. Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (c) 2007 Mike McQuaid <mike@mikemcquaid.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ // Journal Entry #include "journalframe.h" #include <CalendarSupport/Utils> #include <KCalendarCore/Journal> #include <KCalUtils/IncidenceFormatter> #include <Akonadi/Calendar/ETMCalendar> #include <QTextBrowser> #include <KLocalizedString> #include "calendarview_debug.h" #include <QEvent> #include <QHBoxLayout> #include <QPushButton> #include <QFontDatabase> using namespace EventViews; JournalDateView::JournalDateView(const Akonadi::ETMCalendar::Ptr &calendar, QWidget *parent) : QFrame(parent) , mCalendar(calendar) { auto layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); } JournalDateView::~JournalDateView() { } void JournalDateView::setDate(const QDate &date) { mDate = date; Q_EMIT setDateSignal(date); } void JournalDateView::clear() { qDeleteAll(mEntries); mEntries.clear(); } // should only be called by the JournalView now. void JournalDateView::addJournal(const Akonadi::Item &j) { QMap<Akonadi::Item::Id, JournalFrame *>::Iterator pos = mEntries.find(j.id()); if (pos != mEntries.end()) { return; } QWidget *container = new QWidget(this); layout()->addWidget(container); QHBoxLayout *layout = new QHBoxLayout(container); layout->addStretch(1); JournalFrame *entry = new JournalFrame(j, mCalendar, this); layout->addWidget(entry, 3 /*stretch*/); layout->addStretch(1); entry->show(); entry->setDate(mDate); entry->setIncidenceChanger(mChanger); mEntries.insert(j.id(), entry); connect(this, &JournalDateView::setIncidenceChangerSignal, entry, &JournalFrame::setIncidenceChanger); connect(this, &JournalDateView::setDateSignal, entry, &JournalFrame::setDate); connect(entry, &JournalFrame::deleteIncidence, this, &JournalDateView::deleteIncidence); connect(entry, &JournalFrame::editIncidence, this, &JournalDateView::editIncidence); connect(entry, &JournalFrame::incidenceSelected, this, &JournalDateView::incidenceSelected); connect(entry, QOverload<const KCalendarCore::Journal::Ptr &, bool>::of(&JournalFrame::printJournal), this, QOverload<const KCalendarCore::Journal::Ptr &, bool>::of(&JournalDateView::printJournal)); } Akonadi::Item::List JournalDateView::journals() const { Akonadi::Item::List l; l.reserve(mEntries.count()); for (const JournalFrame *const i : qAsConst(mEntries)) { l.push_back(i->journal()); } return l; } void JournalDateView::setIncidenceChanger(Akonadi::IncidenceChanger *changer) { mChanger = changer; Q_EMIT setIncidenceChangerSignal(changer); } void JournalDateView::emitNewJournal() { Q_EMIT newJournal(mDate); } void JournalDateView::journalEdited(const Akonadi::Item &journal) { QMap<Akonadi::Item::Id, JournalFrame *>::Iterator pos = mEntries.find(journal.id()); if (pos == mEntries.end()) { return; } pos.value()->setJournal(journal); } void JournalDateView::journalDeleted(const Akonadi::Item &journal) { QMap<Akonadi::Item::Id, JournalFrame *>::Iterator pos = mEntries.find(journal.id()); if (pos == mEntries.end()) { return; } delete pos.value(); mEntries.remove(journal.id()); } JournalFrame::JournalFrame(const Akonadi::Item &j, const Akonadi::ETMCalendar::Ptr &calendar, QWidget *parent) : QFrame(parent) , mJournal(j) , mCalendar(calendar) { mDirty = false; mWriteInProgress = false; mChanger = nullptr; QVBoxLayout *verticalLayout = new QVBoxLayout(this); mBrowser = new QTextBrowser(this); mBrowser->viewport()->installEventFilter(this); mBrowser->setFrameStyle(QFrame::NoFrame); verticalLayout->addWidget(mBrowser); QHBoxLayout *buttonsLayout = new QHBoxLayout(); verticalLayout->addLayout(buttonsLayout); buttonsLayout->addStretch(); mEditButton = new QPushButton(this); mEditButton->setObjectName(QStringLiteral("editButton")); mEditButton->setText(i18n("&Edit")); mEditButton->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); mEditButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); mEditButton->setToolTip(i18n("Edit this journal entry")); mEditButton->setWhatsThis(i18n("Opens an editor dialog for this journal entry")); buttonsLayout->addWidget(mEditButton); connect(mEditButton, &QPushButton::clicked, this, &JournalFrame::editItem); mDeleteButton = new QPushButton(this); mDeleteButton->setObjectName(QStringLiteral("deleteButton")); mDeleteButton->setText(i18n("&Delete")); mDeleteButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); mDeleteButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); mDeleteButton->setToolTip(i18n("Delete this journal entry")); mDeleteButton->setWhatsThis(i18n("Delete this journal entry")); buttonsLayout->addWidget(mDeleteButton); connect(mDeleteButton, &QPushButton::pressed, this, &JournalFrame::deleteItem); mPrintButton = new QPushButton(this); mPrintButton->setText(i18n("&Print")); mPrintButton->setObjectName(QStringLiteral("printButton")); mPrintButton->setIcon(QIcon::fromTheme(QStringLiteral("document-print"))); mPrintButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); mPrintButton->setToolTip(i18n("Print this journal entry")); mPrintButton->setWhatsThis(i18n("Opens a print dialog for this journal entry")); buttonsLayout->addWidget(mPrintButton); connect(mPrintButton, &QPushButton::clicked, this, QOverload<>::of(&JournalFrame::printJournal)); mPrintPreviewButton = new QPushButton(this); mPrintPreviewButton->setText(i18n("Print preview")); mPrintPreviewButton->setObjectName(QStringLiteral("printButton")); mPrintPreviewButton->setIcon(QIcon::fromTheme(QStringLiteral("document-print-preview"))); mPrintPreviewButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); mPrintPreviewButton->setToolTip(i18n("Print preview this journal entry")); buttonsLayout->addWidget(mPrintPreviewButton); connect(mPrintPreviewButton, &QAbstractButton::clicked, this, &JournalFrame::printPreviewJournal); readJournal(mJournal); mDirty = false; setFrameStyle(QFrame::Box); // These probably shouldn't be hardcoded setStyleSheet(QStringLiteral("QFrame { border: 1px solid; border-radius: 7px; } ")); mBrowser->setStyleSheet(QStringLiteral("QFrame { border: 0px solid white } ")); } JournalFrame::~JournalFrame() { } bool JournalFrame::eventFilter(QObject *object, QEvent *event) { Q_UNUSED(object); // object is our QTextBrowser if (!mJournal.isValid()) { return false; } switch (event->type()) { case QEvent::MouseButtonPress: Q_EMIT incidenceSelected(mJournal, mDate); break; case QEvent::MouseButtonDblClick: Q_EMIT editIncidence(mJournal); break; default: break; } return false; } void JournalFrame::deleteItem() { if (CalendarSupport::hasJournal(mJournal)) { Q_EMIT deleteIncidence(mJournal); } } void JournalFrame::editItem() { if (CalendarSupport::hasJournal(mJournal)) { Q_EMIT editIncidence(mJournal); } } void JournalFrame::setCalendar(const Akonadi::ETMCalendar::Ptr &calendar) { mCalendar = calendar; } void JournalFrame::setDate(const QDate &date) { mDate = date; } void JournalFrame::setJournal(const Akonadi::Item &journal) { if (!CalendarSupport::hasJournal(journal)) { return; } mJournal = journal; readJournal(journal); mDirty = false; } void JournalFrame::setDirty() { mDirty = true; qCDebug(CALENDARVIEW_LOG); } void JournalFrame::printJournal() { Q_EMIT printJournal(CalendarSupport::journal(mJournal), false); } void JournalFrame::printPreviewJournal() { Q_EMIT printJournal(CalendarSupport::journal(mJournal), true); } void JournalFrame::readJournal(const Akonadi::Item &j) { int baseFontSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize(); mJournal = j; const KCalendarCore::Journal::Ptr journal = CalendarSupport::journal(j); mBrowser->clear(); QTextCursor cursor = QTextCursor(mBrowser->textCursor()); cursor.movePosition(QTextCursor::Start); QTextBlockFormat bodyBlock = QTextBlockFormat(cursor.blockFormat()); //FIXME: Do padding bodyBlock.setTextIndent(2); QTextCharFormat bodyFormat = QTextCharFormat(cursor.charFormat()); if (!journal->summary().isEmpty()) { QTextCharFormat titleFormat = bodyFormat; titleFormat.setFontWeight(QFont::Bold); titleFormat.setFontPointSize(baseFontSize + 4); cursor.insertText(journal->summary(), titleFormat); cursor.insertBlock(); } QTextCharFormat dateFormat = bodyFormat; dateFormat.setFontWeight(QFont::Bold); dateFormat.setFontPointSize(baseFontSize + 1); cursor.insertText(KCalUtils::IncidenceFormatter::dateTimeToString( journal->dtStart(), journal->allDay()), dateFormat); cursor.insertBlock(); cursor.insertBlock(); cursor.setBlockCharFormat(bodyFormat); const QString description = journal->description(); if (journal->descriptionIsRich()) { mBrowser->insertHtml(description); } else { mBrowser->insertPlainText(description); } if (mCalendar) { mEditButton->setEnabled(mCalendar->hasRight(j, Akonadi::Collection::CanChangeItem)); mDeleteButton->setEnabled(mCalendar->hasRight(j, Akonadi::Collection::CanDeleteItem)); } } 0707010000002A000081A40000000200000002000000015F0BF3C9000010B9000000000000000000000000000000000000004900000000eventviews-VERSIONgit.20200713T074025~752bb43/src/journal/journalframe.h/* This file is part of KOrganizer. Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (c) 2007 Mike McQuaid <mike@mikemcquaid.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef CALENDARVIEWS_JOURNALFRAME_H #define CALENDARVIEWS_JOURNALFRAME_H #include <Akonadi/Calendar/IncidenceChanger> #include <Akonadi/Calendar/ETMCalendar> #include <Item> #include <QDate> #include <QFrame> class QTextBrowser; class QPushButton; namespace EventViews { class JournalFrame : public QFrame { Q_OBJECT public: typedef QList<JournalFrame *> List; JournalFrame(const Akonadi::Item &journal, const Akonadi::ETMCalendar::Ptr &calendar, QWidget *parent); ~JournalFrame() override; bool eventFilter(QObject *, QEvent *) override; void setJournal(const Akonadi::Item &journal); Q_REQUIRED_RESULT Akonadi::Item journal() const { return mJournal; } void setCalendar(const Akonadi::ETMCalendar::Ptr &); Q_REQUIRED_RESULT QDate date() const { return mDate; } void clear(); void readJournal(const Akonadi::Item &journal); protected Q_SLOTS: void setDirty(); void deleteItem(); void editItem(); void printJournal(); void printPreviewJournal(); public Q_SLOTS: void setIncidenceChanger(Akonadi::IncidenceChanger *changer) { mChanger = changer; } void setDate(const QDate &date); Q_SIGNALS: void printJournal(const KCalendarCore::Journal::Ptr &, bool preview); void deleteIncidence(const Akonadi::Item &); void editIncidence(const Akonadi::Item &); void incidenceSelected(const Akonadi::Item &, const QDate &); protected: void clearFields(); private: Akonadi::Item mJournal; Akonadi::ETMCalendar::Ptr mCalendar; QDate mDate; QTextBrowser *mBrowser = nullptr; QPushButton *mEditButton = nullptr; QPushButton *mDeleteButton = nullptr; QPushButton *mPrintButton = nullptr; QPushButton *mPrintPreviewButton = nullptr; bool mDirty; bool mWriteInProgress; Akonadi::IncidenceChanger *mChanger = nullptr; }; class JournalDateView : public QFrame { Q_OBJECT public: typedef QList<JournalDateView *> List; JournalDateView(const Akonadi::ETMCalendar::Ptr &, QWidget *parent); ~JournalDateView(); void addJournal(const Akonadi::Item &journal); Akonadi::Item::List journals() const; void setDate(const QDate &date); QDate date() const { return mDate; } void clear(); Q_SIGNALS: void setIncidenceChangerSignal(Akonadi::IncidenceChanger *changer); void setDateSignal(const QDate &); void flushEntries(); void editIncidence(const Akonadi::Item &journal); void deleteIncidence(const Akonadi::Item &journal); void newJournal(const QDate &); void incidenceSelected(const Akonadi::Item &, const QDate &); void printJournal(const KCalendarCore::Journal::Ptr &, bool preview); public Q_SLOTS: void emitNewJournal(); void setIncidenceChanger(Akonadi::IncidenceChanger *changer); void journalEdited(const Akonadi::Item &); void journalDeleted(const Akonadi::Item &); private: Akonadi::ETMCalendar::Ptr mCalendar; QDate mDate; QMap<Akonadi::Item::Id, JournalFrame *> mEntries; Akonadi::IncidenceChanger *mChanger = nullptr; }; } #endif 0707010000002B000081A40000000200000002000000015F0BF3C900001BDE000000000000000000000000000000000000004A00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/journal/journalview.cpp/* This file is part of KOrganizer. Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ // View of Journal entries #include "journalview.h" #include "journalframe.h" #include <CalendarSupport/Utils> #include <QVBoxLayout> #include "calendarview_debug.h" #include <QEvent> #include <QScrollArea> using namespace EventViews; JournalView::JournalView(QWidget *parent) : EventView(parent) { QVBoxLayout *topLayout = new QVBoxLayout(this); topLayout->setContentsMargins(0, 0, 0, 0); mSA = new QScrollArea(this); mCurrentWidget = new QWidget(mSA->viewport()); QVBoxLayout *mVBoxVBoxLayout = new QVBoxLayout(mCurrentWidget); mVBoxVBoxLayout->setContentsMargins(0, 0, 0, 0); mSA->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); mSA->setWidgetResizable(true); mSA->setWidget(mCurrentWidget); topLayout->addWidget(mSA); installEventFilter(this); } JournalView::~JournalView() { } void JournalView::appendJournal(const Akonadi::Item &journal, const QDate &dt) { JournalDateView *entry = nullptr; if (mEntries.contains(dt)) { entry = mEntries[dt]; } else { entry = new JournalDateView(calendar(), mCurrentWidget); mCurrentWidget->layout()->addWidget(entry); entry->setDate(dt); entry->setIncidenceChanger(mChanger); entry->show(); connect(this, &JournalView::flushEntries, entry, &JournalDateView::flushEntries); connect(this, &JournalView::setIncidenceChangerSignal, entry, &JournalDateView::setIncidenceChanger); connect(this, &JournalView::journalEdited, entry, &JournalDateView::journalEdited); connect(this, &JournalView::journalDeleted, entry, &JournalDateView::journalDeleted); connect(entry, &JournalDateView::editIncidence, this, &EventView::editIncidenceSignal); connect(entry, &JournalDateView::deleteIncidence, this, &EventView::deleteIncidenceSignal); connect(entry, &JournalDateView::newJournal, this, &EventView::newJournalSignal); connect(entry, &JournalDateView::incidenceSelected, this, &EventView::incidenceSelected); connect(entry, &JournalDateView::printJournal, this, &JournalView::printJournal); mEntries.insert(dt, entry); } if (entry && CalendarSupport::hasJournal(journal)) { entry->addJournal(journal); } } int JournalView::currentDateCount() const { return mEntries.size(); } Akonadi::Item::List JournalView::selectedIncidences() const { // We don't have a selection in the journal view. // FIXME: The currently edited journal is the selected incidence... Akonadi::Item::List eventList; return eventList; } void JournalView::clearEntries() { //qDebug() << "JournalView::clearEntries()"; QMap<QDate, JournalDateView *>::Iterator it; for (it = mEntries.begin(); it != mEntries.end(); ++it) { delete it.value(); } mEntries.clear(); } void JournalView::updateView() { QMap<QDate, JournalDateView *>::Iterator it = mEntries.end(); while (it != mEntries.begin()) { --it; it.value()->clear(); const KCalendarCore::Journal::List journals = calendar()->journals(it.key()); qCDebug(CALENDARVIEW_LOG) << "updateview found" << journals.count(); for (const KCalendarCore::Journal::Ptr &journal : journals) { Akonadi::Item item = calendar()->item(journal); it.value()->addJournal(item); } } } void JournalView::flushView() { Q_EMIT flushEntries(); } void JournalView::showDates(const QDate &start, const QDate &end, const QDate &) { clearEntries(); if (end < start) { qCWarning(CALENDARVIEW_LOG) << "End is smaller than start. end=" << end << "; start=" << start; return; } for (QDate d = end; d >= start; d = d.addDays(-1)) { const KCalendarCore::Journal::List jnls = calendar()->journals(d); //qCDebug(CALENDARVIEW_LOG) << "Found" << jnls.count() << "journals on date" << d; for (const KCalendarCore::Journal::Ptr &journal : jnls) { Akonadi::Item item = calendar()->item(journal); appendJournal(item, d); } if (jnls.isEmpty()) { // create an empty dateentry widget //updateView(); //qCDebug(CALENDARVIEW_LOG) << "Appended null journal"; appendJournal(Akonadi::Item(), d); } } } void JournalView::showIncidences(const Akonadi::Item::List &incidences, const QDate &date) { Q_UNUSED(date); clearEntries(); for (const Akonadi::Item &i : incidences) { if (const KCalendarCore::Journal::Ptr j = CalendarSupport::journal(i)) { appendJournal(i, j->dtStart().date()); } } } void JournalView::changeIncidenceDisplay(const Akonadi::Item &incidence, Akonadi::IncidenceChanger::ChangeType changeType) { if (KCalendarCore::Journal::Ptr journal = CalendarSupport::journal(incidence)) { switch (changeType) { case Akonadi::IncidenceChanger::ChangeTypeCreate: appendJournal(incidence, journal->dtStart().date()); break; case Akonadi::IncidenceChanger::ChangeTypeModify: Q_EMIT journalEdited(incidence); break; case Akonadi::IncidenceChanger::ChangeTypeDelete: Q_EMIT journalDeleted(incidence); break; default: qCWarning(CALENDARVIEW_LOG) << "Illegal change type" << changeType; } } } void JournalView::setIncidenceChanger(Akonadi::IncidenceChanger *changer) { mChanger = changer; Q_EMIT setIncidenceChangerSignal(changer); } void JournalView::newJournal() { Q_EMIT newJournalSignal(QDate::currentDate()); } bool JournalView::eventFilter(QObject *object, QEvent *event) { Q_UNUSED(object); switch (event->type()) { case QEvent::MouseButtonDblClick: Q_EMIT newJournalSignal(QDate()); return true; default: return false; } } 0707010000002C000081A40000000200000002000000015F0BF3C900000D9E000000000000000000000000000000000000004800000000eventviews-VERSIONgit.20200713T074025~752bb43/src/journal/journalview.h/* This file is part of KOrganizer. Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef CALENDARVIEWS_JOURNALVIEW_H #define CALENDARVIEWS_JOURNALVIEW_H #include "eventview.h" #include <Akonadi/Calendar/IncidenceChanger> #include <KCalendarCore/Journal> class QScrollArea; /** * This class provides a journal view. * @short View for Journal components. * @author Cornelius Schumacher <schumacher@kde.org>, Reinhold Kainhofer <reinhold@kainhofer.com> * @see KOBaseView */ namespace EventViews { class JournalDateView; class EVENTVIEWS_EXPORT JournalView : public EventView { Q_OBJECT public: explicit JournalView(QWidget *parent = nullptr); ~JournalView() override; Q_REQUIRED_RESULT int currentDateCount() const override; Q_REQUIRED_RESULT Akonadi::Item::List selectedIncidences() const override; Q_REQUIRED_RESULT KCalendarCore::DateList selectedIncidenceDates() const override { return KCalendarCore::DateList(); } void appendJournal(const Akonadi::Item &journal, const QDate &dt); /** documentation in baseview.h */ void getHighlightMode(bool &highlightEvents, bool &highlightTodos, bool &highlightJournals); bool eventFilter(QObject *, QEvent *) override; public Q_SLOTS: // Don't update the view when midnight passed, otherwise we'll have data loss (bug 79145) void dayPassed(const QDate &) override { } void updateView() override; void flushView() override; void showDates(const QDate &start, const QDate &end, const QDate &preferredMonth = QDate()) override; void showIncidences(const Akonadi::Item::List &incidences, const QDate &date) override; void changeIncidenceDisplay(const Akonadi::Item & incidence, Akonadi::IncidenceChanger::ChangeType); void setIncidenceChanger(Akonadi::IncidenceChanger *changer) override; void newJournal(); Q_SIGNALS: void flushEntries(); void setIncidenceChangerSignal(Akonadi::IncidenceChanger *); void journalEdited(const Akonadi::Item &journal); void journalDeleted(const Akonadi::Item &journal); void printJournal(const KCalendarCore::Journal::Ptr &, bool preview); protected: void clearEntries(); private: QScrollArea *mSA = nullptr; QWidget *mCurrentWidget = nullptr; QMap<QDate, EventViews::JournalDateView *> mEntries; Akonadi::IncidenceChanger *mChanger = nullptr; // DateList mSelectedDates; // List of dates to be displayed }; } #endif 0707010000002D000041ED0000000200000002000000025F0BF3C900000000000000000000000000000000000000000000003700000000eventviews-VERSIONgit.20200713T074025~752bb43/src/list0707010000002E000081A40000000200000002000000015F0BF3C900005007000000000000000000000000000000000000004400000000eventviews-VERSIONgit.20200713T074025~752bb43/src/list/listview.cpp/* Copyright (c) 1999 Preston Brown <pbrown@kde.org> Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org> Copyright (c) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (c) 2010 Sérgio Martins <iamsergio@gmail.com> Copyright (c) 2012-2013 Allen Winter <winter@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ //TODO: put a reminder and/or recurs icon on the item? #include "listview.h" #include "helper.h" #include <CalendarSupport/Utils> #include <Akonadi/Calendar/ETMCalendar> #include <Akonadi/Calendar/IncidenceChanger> #include <KCalUtils/IncidenceFormatter> #include <KCalendarCore/Visitor> #include <KConfig> #include <KConfigGroup> #include "calendarview_debug.h" #include <QBoxLayout> #include <QHeaderView> #include <QIcon> #include <QTreeWidget> #include <QLocale> using namespace EventViews; using namespace KCalendarCore; using namespace KCalUtils; enum { Summary_Column = 0, StartDateTime_Column, EndDateTime_Column, Categories_Column, Dummy_EOF_Column // Dummy enum value for iteration purposes only. Always keep at the end. }; static const QTime DAY_START {00, 00}; static const QTime DAY_END {23, 59, 59, 999}; static QString cleanSummary(const QString &summary, const QDateTime &next) { QString retStr = summary; retStr.replace(QLatin1Char('\n'), QLatin1Char(' ')); if (next.isValid()) { const QString dateStr = QLocale().toString(next.date(), QLocale::ShortFormat); retStr = i18nc("%1 is an item summary. %2 is the date when this item reoccurs", "%1 (next: %2)", retStr, dateStr); } return retStr; } class ListViewItem : public QTreeWidgetItem { public: ListViewItem(const Akonadi::Item &incidence, QTreeWidget *parent) : QTreeWidgetItem(parent) , mTreeWidget(parent) , mIncidence(incidence) { } bool operator<(const QTreeWidgetItem &other) const override; const QTreeWidget *mTreeWidget = nullptr; const Akonadi::Item mIncidence; QDateTime start; QDateTime end; }; bool ListViewItem::operator<(const QTreeWidgetItem &other) const { const ListViewItem *otheritem = static_cast<const ListViewItem *>(&other); switch (treeWidget()->sortColumn()) { case StartDateTime_Column: // Missing start times are sorted before any defined time. return otheritem->start.isValid() && (!start.isValid() || start < otheritem->start); case EndDateTime_Column: // Missing end times are sorted after any defined time. return end.isValid() && (!otheritem->end.isValid() || end < otheritem->end); default: return QTreeWidgetItem::operator <(other); } } class Q_DECL_HIDDEN ListView::Private { public: Private() { } ~Private() { } void addIncidences(const Akonadi::ETMCalendar::Ptr &calendar, const KCalendarCore::Incidence::List &incidenceList, const QDate &date); void addIncidence(const Akonadi::ETMCalendar::Ptr &calendar, const KCalendarCore::Incidence::Ptr &, const QDate &date); void addIncidence(const Akonadi::ETMCalendar::Ptr &calendar, const Akonadi::Item &, const QDate &date); ListViewItem *getItemForIncidence(const Akonadi::Item &); QTreeWidget *mTreeWidget = nullptr; ListViewItem *mActiveItem = nullptr; QHash<Akonadi::Item::Id, Akonadi::Item> mItems; QHash<Akonadi::Item::Id, QDate> mDateList; QDate mStartDate; QDate mEndDate; DateList mSelectedDates; // if it's non interactive we disable context menu, and incidence editing bool mIsNonInteractive; class ListItemVisitor; }; /** This class provides the initialization of a ListViewItem for calendar components using the Incidence::Visitor. */ class ListView::Private::ListItemVisitor : public KCalendarCore::Visitor { public: ListItemVisitor(ListViewItem *item, QDate dt) : mItem(item) , mStartDate(dt) { } ~ListItemVisitor() override { } bool visit(const Event::Ptr &) override; bool visit(const Todo::Ptr &) override; bool visit(const Journal::Ptr &) override; bool visit(const FreeBusy::Ptr &) override { // to inhibit hidden virtual compile warning return true; } private: ListViewItem *mItem = nullptr; QDate mStartDate; }; bool ListView::Private::ListItemVisitor::visit(const Event::Ptr &e) { QIcon eventPxmp; if (e->customProperty("KABC", "ANNIVERSARY") == QLatin1String("YES")) { eventPxmp = QIcon::fromTheme(QStringLiteral("view-calendar-wedding-anniversary")); } else if (e->customProperty("KABC", "BIRTHDAY") == QLatin1String("YES")) { eventPxmp = QIcon::fromTheme(QStringLiteral("view-calendar-birthday")); } else { eventPxmp = QIcon::fromTheme(e->iconName()); } mItem->setIcon(Summary_Column, eventPxmp); QDateTime next; if (e->recurs()) { const qint64 duration = e->dtStart().secsTo(e->dtEnd()); QDateTime kdt(mStartDate, DAY_START); kdt = kdt.addMSecs(-1); mItem->start = e->recurrence()->getNextDateTime(kdt).toLocalTime(); mItem->end = mItem->start.addSecs(duration); next = e->recurrence()->getNextDateTime(mItem->start).toLocalTime(); } else { mItem->start = e->dtStart().toLocalTime(); mItem->end = e->dtEnd().toLocalTime(); } mItem->setText(Summary_Column, cleanSummary(e->summary(), next)); if (e->allDay()) { mItem->start.setTime(DAY_START); mItem->end.setTime(DAY_END); mItem->setText(StartDateTime_Column, QLocale().toString(mItem->start.date(), QLocale::ShortFormat)); mItem->setText(EndDateTime_Column, QLocale().toString(mItem->end.date(), QLocale::ShortFormat)); } else { mItem->setText(StartDateTime_Column, QLocale().toString(mItem->start, QLocale::ShortFormat)); mItem->setText(EndDateTime_Column, QLocale().toString(mItem->end, QLocale::ShortFormat)); } mItem->setText(Categories_Column, e->categoriesStr()); return true; } bool ListView::Private::ListItemVisitor::visit(const Todo::Ptr &t) { mItem->setIcon(Summary_Column, QIcon::fromTheme(t->iconName())); if (t->recurs()) { QDateTime kdt(mStartDate, DAY_START); kdt = kdt.addMSecs(-1); mItem->start = t->recurrence()->getNextDateTime(kdt).toLocalTime(); if (t->hasDueDate()) { const qint64 duration = t->dtStart().secsTo(t->dtDue()); mItem->end = mItem->start.addSecs(duration); } else { mItem->end = QDateTime(); } } else { mItem->start = t->hasStartDate() ? t->dtStart().toLocalTime() : QDateTime(); mItem->end = t->hasDueDate() ? t->dtDue().toLocalTime() : QDateTime(); } if (t->allDay()) { mItem->start.setTime(DAY_START); mItem->end.setTime(DAY_END); } mItem->setText(Summary_Column, cleanSummary(t->summary(), QDateTime())); if (t->hasStartDate()) { if (t->allDay()) { mItem->setText(StartDateTime_Column, QLocale().toString(t->dtStart().toLocalTime().date(), QLocale::ShortFormat)); } else { mItem->setText(StartDateTime_Column, QLocale().toString(t->dtStart().toLocalTime(), QLocale::ShortFormat)); } } else { mItem->setText(StartDateTime_Column, QStringLiteral("---")); } if (t->hasDueDate()) { if (t->allDay()) { mItem->setText(EndDateTime_Column, QLocale().toString(t->dtDue().toLocalTime().date(), QLocale::ShortFormat)); } else { mItem->setText(EndDateTime_Column, QLocale().toString(t->dtDue().toLocalTime(), QLocale::ShortFormat)); } } else { mItem->setText(EndDateTime_Column, QStringLiteral("---")); } mItem->setText(Categories_Column, t->categoriesStr()); return true; } bool ListView::Private::ListItemVisitor::visit(const Journal::Ptr &j) { mItem->setIcon(Summary_Column, QIcon::fromTheme(j->iconName())); mItem->start = j->dtStart(); mItem->end = QDateTime(); if (j->summary().isEmpty()) { mItem->setText(Summary_Column, cleanSummary(j->description().section(QLatin1Char('\n'), 0, 0), QDateTime())); } else { mItem->setText(Summary_Column, cleanSummary(j->summary(), QDateTime())); } if (j->allDay()) { mItem->start.setTime(DAY_START); mItem->setText(StartDateTime_Column, QLocale().toString(j->dtStart().toLocalTime().date(), QLocale::ShortFormat)); } else { mItem->setText(StartDateTime_Column, QLocale().toString(j->dtStart().toLocalTime(), QLocale::ShortFormat)); } mItem->setText(EndDateTime_Column, QStringLiteral("---")); mItem->setText(Categories_Column, j->categoriesStr()); return true; } ListView::ListView(const Akonadi::ETMCalendar::Ptr &calendar, QWidget *parent, bool nonInteractive) : EventView(parent) , d(new Private()) { setCalendar(calendar); d->mActiveItem = nullptr; d->mIsNonInteractive = nonInteractive; d->mTreeWidget = new QTreeWidget(this); d->mTreeWidget->setColumnCount(4); d->mTreeWidget->setSortingEnabled(true); d->mTreeWidget->headerItem()->setText(Summary_Column, i18n("Summary")); d->mTreeWidget->headerItem()->setText(StartDateTime_Column, i18n("Start Date/Time")); d->mTreeWidget->headerItem()->setText(EndDateTime_Column, i18n("End Date/Time")); d->mTreeWidget->headerItem()->setText(Categories_Column, i18n("Categories")); d->mTreeWidget->setWordWrap(true); d->mTreeWidget->setAllColumnsShowFocus(true); d->mTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu); d->mTreeWidget->setRootIsDecorated(false); QBoxLayout *layoutTop = new QVBoxLayout(this); layoutTop->setContentsMargins(0, 0, 0, 0); layoutTop->addWidget(d->mTreeWidget); QObject::connect(d->mTreeWidget, QOverload<const QModelIndex &>::of(&QTreeWidget::doubleClicked), this, QOverload<const QModelIndex &>::of(&ListView::defaultItemAction)); QObject::connect(d->mTreeWidget, &QTreeWidget::customContextMenuRequested, this, &ListView::popupMenu); QObject::connect(d->mTreeWidget, &QTreeWidget::itemSelectionChanged, this, &ListView::processSelectionChange); d->mSelectedDates.append(QDate::currentDate()); updateView(); } ListView::~ListView() { delete d; } int ListView::currentDateCount() const { return d->mSelectedDates.count(); } Akonadi::Item::List ListView::selectedIncidences() const { Akonadi::Item::List eventList; QTreeWidgetItem *item = d->mTreeWidget->selectedItems().isEmpty() ? nullptr : d->mTreeWidget->selectedItems().first(); if (item) { ListViewItem *i = static_cast<ListViewItem *>(item); eventList.append(i->mIncidence); } return eventList; } DateList ListView::selectedIncidenceDates() const { return d->mSelectedDates; } void ListView::updateView() { static int maxLen = 38; /* Set the width of the summary column to show 'maxlen' chars, at most */ int width = qMin(maxLen * fontMetrics().averageCharWidth(), maxLen * 12); width += 24; //for the icon d->mTreeWidget->setColumnWidth(Summary_Column, width); for (int col = StartDateTime_Column; col < Dummy_EOF_Column; ++col) { d->mTreeWidget->resizeColumnToContents(col); } d->mTreeWidget->sortItems(StartDateTime_Column, Qt::AscendingOrder); } void ListView::showDates(const QDate &start, const QDate &end, const QDate &preferredMonth) { Q_UNUSED(preferredMonth); clear(); d->mStartDate = start; d->mEndDate = end; const QString startStr = QLocale().toString(start, QLocale::ShortFormat); const QString endStr = QLocale().toString(end, QLocale::ShortFormat); d->mTreeWidget->headerItem()->setText(Summary_Column, i18n("Summary [%1 - %2]", startStr, endStr)); QDate date = start; while (date <= end) { d->addIncidences(calendar(), calendar()->incidences(date), date); d->mSelectedDates.append(date); date = date.addDays(1); } updateView(); Q_EMIT incidenceSelected(Akonadi::Item(), QDate()); } void ListView::showAll() { d->addIncidences(calendar(), calendar()->incidences(), QDate()); } void ListView::Private::addIncidences(const Akonadi::ETMCalendar::Ptr &calendar, const KCalendarCore::Incidence::List &incidences, const QDate &date) { for (const KCalendarCore::Incidence::Ptr &incidence : incidences) { addIncidence(calendar, incidence, date); } } void ListView::Private::addIncidence(const Akonadi::ETMCalendar::Ptr &calendar, const Akonadi::Item &item, const QDate &date) { Q_ASSERT(calendar); if (item.isValid() && item.hasPayload<KCalendarCore::Incidence::Ptr>()) { addIncidence(calendar, item.payload<KCalendarCore::Incidence::Ptr>(), date); } } void ListView::Private::addIncidence(const Akonadi::ETMCalendar::Ptr &calendar, const KCalendarCore::Incidence::Ptr &incidence, const QDate &date) { if (!incidence) { return; } Akonadi::Item aitem = calendar->item(incidence); if (!aitem.isValid() || mItems.contains(aitem.id())) { return; } mDateList.insert(aitem.id(), date); mItems.insert(aitem.id(), aitem); Incidence::Ptr tinc = incidence; if (tinc->customProperty("KABC", "BIRTHDAY") == QLatin1String("YES") || tinc->customProperty("KABC", "ANNIVERSARY") == QLatin1String("YES")) { const int years = EventViews::yearDiff(tinc->dtStart().date(), mEndDate); if (years > 0) { tinc = Incidence::Ptr(incidence->clone()); tinc->setReadOnly(false); tinc->setSummary(i18np("%2 (1 year)", "%2 (%1 years)", years, cleanSummary(incidence->summary(), QDateTime()))); tinc->setReadOnly(true); } } ListViewItem *item = new ListViewItem(aitem, mTreeWidget); // set tooltips for (int col = 0; col < Dummy_EOF_Column; ++col) { item->setToolTip(col, IncidenceFormatter::toolTipStr( CalendarSupport::displayName(calendar.data(), aitem.parentCollection()), incidence)); } ListItemVisitor v(item, mStartDate); if (!tinc->accept(v, tinc)) { delete item; return; } item->setData(0, Qt::UserRole, QVariant(aitem.id())); } void ListView::showIncidences(const Akonadi::Item::List &itemList, const QDate &date) { clear(); d->addIncidences(calendar(), CalendarSupport::incidencesFromItems(itemList), date); updateView(); // After new creation of list view no events are selected. Q_EMIT incidenceSelected(Akonadi::Item(), date); } void ListView::changeIncidenceDisplay(const Akonadi::Item &aitem, int action) { const Incidence::Ptr incidence = CalendarSupport::incidence(aitem); ListViewItem *item = nullptr; QDate f = d->mSelectedDates.first(); QDate l = d->mSelectedDates.last(); QDate date; if (CalendarSupport::hasTodo(aitem)) { date = CalendarSupport::todo(aitem)->dtDue().toLocalTime().date(); } else { date = incidence->dtStart().toLocalTime().date(); } switch (action) { case Akonadi::IncidenceChanger::ChangeTypeCreate: if (date >= f && date <= l) { d->addIncidence(calendar(), aitem, date); } break; case Akonadi::IncidenceChanger::ChangeTypeModify: item = d->getItemForIncidence(aitem); if (item) { delete item; d->mItems.remove(aitem.id()); d->mDateList.remove(aitem.id()); } if (date >= f && date <= l) { d->addIncidence(calendar(), aitem, date); } break; case Akonadi::IncidenceChanger::ChangeTypeDelete: item = d->getItemForIncidence(aitem); delete item; break; default: qCDebug(CALENDARVIEW_LOG) << "Illegal action" << action; } } ListViewItem *ListView::Private::getItemForIncidence(const Akonadi::Item &aitem) { int index = 0; while (QTreeWidgetItem *it = mTreeWidget->topLevelItem(index)) { ListViewItem *item = static_cast<ListViewItem *>(it); if (item->mIncidence.id() == aitem.id()) { return item; } ++index; } return nullptr; } void ListView::defaultItemAction(const QModelIndex &index) { if (!d->mIsNonInteractive) { // Get the first column, it has our Akonadi::Id const QModelIndex col0Idx = d->mTreeWidget->model()->index(index.row(), 0); Akonadi::Item::Id id = d->mTreeWidget->model()->data(col0Idx, Qt::UserRole).toLongLong(); defaultAction(d->mItems.value(id)); } } void ListView::defaultItemAction(const Akonadi::Item::Id id) { if (!d->mIsNonInteractive) { defaultAction(d->mItems.value(id)); } } void ListView::popupMenu(const QPoint &point) { d->mActiveItem = static_cast<ListViewItem *>(d->mTreeWidget->itemAt(point)); if (d->mActiveItem && !d->mIsNonInteractive) { const Akonadi::Item aitem = d->mActiveItem->mIncidence; // FIXME: For recurring incidences we don't know the date of this // occurrence, there's no reference to it at all! Q_EMIT showIncidencePopupSignal(aitem, CalendarSupport::incidence(aitem)->dtStart().date()); } else { Q_EMIT showNewEventPopupSignal(); } } void ListView::readSettings(KConfig *config) { KConfigGroup cfgGroup = config->group("ListView Layout"); const QByteArray state = cfgGroup.readEntry("ViewState", QByteArray()); d->mTreeWidget->header()->restoreState(state); } void ListView::writeSettings(KConfig *config) { const QByteArray state = d->mTreeWidget->header()->saveState(); KConfigGroup cfgGroup = config->group("ListView Layout"); cfgGroup.writeEntry("ViewState", state); } void ListView::processSelectionChange() { if (!d->mIsNonInteractive) { ListViewItem *item; if (d->mTreeWidget->selectedItems().isEmpty()) { item = nullptr; } else { item = static_cast<ListViewItem *>(d->mTreeWidget->selectedItems().first()); } if (!item) { Q_EMIT incidenceSelected(Akonadi::Item(), QDate()); } else { Q_EMIT incidenceSelected(item->mIncidence, d->mDateList.value(item->mIncidence.id())); } } } void ListView::clearSelection() { d->mTreeWidget->clearSelection(); } void ListView::clear() { d->mSelectedDates.clear(); d->mTreeWidget->clear(); d->mDateList.clear(); d->mItems.clear(); } QSize ListView::sizeHint() const { const QSize s = EventView::sizeHint(); return QSize(s.width() + style()->pixelMetric(QStyle::PM_ScrollBarExtent) + 1, s.height()); } 0707010000002F000081A40000000200000002000000015F0BF3C900000BAD000000000000000000000000000000000000004200000000eventviews-VERSIONgit.20200713T074025~752bb43/src/list/listview.h/* Copyright (c) 1999 Preston Brown <pbrown@kde.org> Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_LISTVIEW_H #define EVENTVIEWS_LISTVIEW_H #include "eventview.h" class KConfig; class QModelIndex; /** This class provides a multi-column list view of events. It can display events from one particular day or several days, it doesn't matter. To use a view that only handles one day at a time, use KODayListView. @short multi-column list view of various events. @author Preston Brown <pbrown@kde.org> @see KOBaseView, KODayListView */ namespace EventViews { class EVENTVIEWS_EXPORT ListView : public EventView { Q_OBJECT public: explicit ListView(const Akonadi::ETMCalendar::Ptr &calendar, QWidget *parent = nullptr, bool nonInteractive = false); ~ListView() override; Q_REQUIRED_RESULT int currentDateCount() const override; Q_REQUIRED_RESULT Akonadi::Item::List selectedIncidences() const override; Q_REQUIRED_RESULT KCalendarCore::DateList selectedIncidenceDates() const override; // Shows all incidences of the calendar void showAll(); void readSettings(KConfig *config); void writeSettings(KConfig *config); void clear(); QSize sizeHint() const override; public Q_SLOTS: void updateView() override; void showDates(const QDate &start, const QDate &end, const QDate &preferredMonth = QDate()) override; void showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) override; void clearSelection() override; void changeIncidenceDisplay(const Akonadi::Item &, int); void defaultItemAction(const QModelIndex &); void defaultItemAction(const Akonadi::Item::Id id); void popupMenu(const QPoint &); Q_SIGNALS: void showNewEventPopupSignal(); void showIncidencePopupSignal(const Akonadi::Item &, const QDate &); protected Q_SLOTS: void processSelectionChange(); private: class Private; Private *const d; }; } #endif 07070100000030000041ED0000000200000002000000025F0BF3C900000000000000000000000000000000000000000000003800000000eventviews-VERSIONgit.20200713T074025~752bb43/src/month07070100000031000081A40000000200000002000000015F0BF3C900002D77000000000000000000000000000000000000004F00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/month/monthgraphicsitems.cpp/* Copyright (c) 2008 Bruno Virlet <bruno.virlet@gmail.com> Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "monthgraphicsitems.h" #include "eventview.h" #include "helper.h" #include "monthitem.h" #include "monthscene.h" #include "monthview.h" #include "prefs.h" #include <QGraphicsScene> #include <QPainter> using namespace EventViews; ScrollIndicator::ScrollIndicator(ScrollIndicator::ArrowDirection dir) : mDirection(dir) { setZValue(200); // on top of everything hide(); } QRectF ScrollIndicator::boundingRect() const { return QRectF(-mWidth / 2, -mHeight / 2, mWidth, mHeight); } void ScrollIndicator::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->setRenderHint(QPainter::Antialiasing); QPolygon arrow(3); if (mDirection == ScrollIndicator::UpArrow) { arrow.setPoint(0, 0, -mHeight / 2); arrow.setPoint(1, mWidth / 2, mHeight / 2); arrow.setPoint(2, -mWidth / 2, mHeight / 2); } else if (mDirection == ScrollIndicator::DownArrow) { // down arrow.setPoint(1, mWidth / 2, -mHeight / 2); arrow.setPoint(2, -mWidth / 2, -mHeight / 2); arrow.setPoint(0, 0, mHeight / 2); } QColor color(QPalette().color(QPalette::WindowText)); color.setAlpha(155); painter->setBrush(color); painter->setPen(color); painter->drawPolygon(arrow); } //------------------------------------------------------------- MonthCell::MonthCell(int id, const QDate &date, QGraphicsScene *scene) : mId(id) , mDate(date) , mScene(scene) { mUpArrow = new ScrollIndicator(ScrollIndicator::UpArrow); mDownArrow = new ScrollIndicator(ScrollIndicator::DownArrow); mScene->addItem(mUpArrow); mScene->addItem(mDownArrow); } MonthCell::~MonthCell() { mScene->removeItem(mUpArrow); mScene->removeItem(mDownArrow); delete mUpArrow; // we've taken ownership, so this is safe delete mDownArrow; } bool MonthCell::hasEventBelow(int height) { if (mHeightHash.isEmpty()) { return false; } for (int i = 0; i < height; ++i) { if (mHeightHash.value(i) != nullptr) { return true; } } return false; } int MonthCell::topMargin() { return 18; } void MonthCell::addMonthItem(MonthItem *manager, int height) { mHeightHash[ height ] = manager; } int MonthCell::firstFreeSpace() { MonthItem *manager = nullptr; int i = 0; while (true) { manager = mHeightHash[ i ]; if (manager == nullptr) { return i; } i++; } } //------------------------------------------------------------- // MONTHGRAPHICSITEM static const int ft = 1; // frame thickness MonthGraphicsItem::MonthGraphicsItem(MonthItem *manager) : QGraphicsItem(nullptr) , mMonthItem(manager) { manager->monthScene()->addItem(this); QTransform transform; transform = transform.translate(0.5, 0.5); setTransform(transform); } MonthGraphicsItem::~MonthGraphicsItem() { } bool MonthGraphicsItem::isMoving() const { return mMonthItem->isMoving(); } bool MonthGraphicsItem::isEndItem() const { return startDate().addDays(daySpan()) == mMonthItem->endDate(); } bool MonthGraphicsItem::isBeginItem() const { return startDate() == mMonthItem->startDate(); } QPainterPath MonthGraphicsItem::shape() const { // The returned shape must be a closed path, // otherwise MonthScene:itemAt(pos) can have // problems detecting the item return widgetPath(false); } // TODO: remove this method. QPainterPath MonthGraphicsItem::widgetPath(bool border) const { // If border is set we won't draw all the path. Items spanning on multiple // rows won't have borders on their boundaries. // If this is the mask, we draw it one pixel bigger const int x0 = (!border && !isBeginItem())? -1 : 0; const int y0 = 0; const int x1 = static_cast<int>(boundingRect().width()); const int y1 = static_cast<int>(boundingRect().height()); const int beginRound = 2; const int margin = 1; QPainterPath path(QPoint(x0 + beginRound, y0)); if (isBeginItem()) { path.quadTo(QPoint(x0 + margin, y0), QPoint(x0 + margin, y0 + beginRound)); path.lineTo(QPoint(x0 + margin, y1 - beginRound)); path.quadTo(QPoint(x0 + margin, y1), QPoint(x0 + beginRound + margin, y1)); } else { path.lineTo(x0, y0); if (!border) { path.lineTo(x0, y1); } else { path.moveTo(x0, y1); } path.lineTo(x0 + beginRound, y1); } if (isEndItem()) { path.lineTo(x1 - beginRound, y1); path.quadTo(QPoint(x1 - margin, y1), QPoint(x1 - margin, y1 - beginRound)); path.lineTo(QPoint(x1 - margin, y0 + beginRound)); path.quadTo(QPoint(x1 - margin, y0), QPoint(x1 - margin - beginRound, y0)); } else { path.lineTo(x1, y1); if (!border) { path.lineTo(x1, y0); } else { path.moveTo(x1, y0); } } // close path path.lineTo(x0 + beginRound, y0); return path; } QRectF MonthGraphicsItem::boundingRect() const { // width - 2 because of the cell-dividing line with width == 1 at beginning and end return QRectF(0, 0, (daySpan() + 1) * mMonthItem->monthScene()->columnWidth() - 2, mMonthItem->monthScene()->itemHeight()); } void MonthGraphicsItem::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) { if (!mMonthItem->monthScene()->initialized()) { return; } MonthScene *scene = mMonthItem->monthScene(); int textMargin = 7; QColor bgColor = mMonthItem->bgColor(); bgColor = mMonthItem->selected() ? bgColor.lighter(EventView::BRIGHTNESS_FACTOR) : bgColor; QColor frameColor = mMonthItem->frameColor(); frameColor = mMonthItem->selected() ? frameColor.lighter(EventView::BRIGHTNESS_FACTOR) : frameColor; QColor textColor = EventViews::getTextColor(bgColor); // make moving or resizing items translucent if (mMonthItem->isMoving() || mMonthItem->isResizing()) { bgColor.setAlphaF(0.75f); } // draw the widget without border p->setRenderHint(QPainter::Antialiasing, false); p->setBrush(bgColor); p->setPen(Qt::NoPen); p->drawPath(widgetPath(false)); p->setRenderHint(QPainter::Antialiasing, true); // draw the border without fill const QPen pen(frameColor, ft, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); p->setPen(pen); p->setBrush(Qt::NoBrush); p->drawPath(widgetPath(true)); // draw text p->setPen(textColor); int alignFlag = Qt::AlignVCenter; if (isBeginItem()) { alignFlag |= Qt::AlignLeft; } else if (isEndItem()) { alignFlag |= Qt::AlignRight; } else { alignFlag |= Qt::AlignHCenter; } // !isBeginItem() is not always isEndItem() QString text = mMonthItem->text(!isBeginItem()); p->setFont(mMonthItem->monthScene()->monthView()->preferences()->monthViewFont()); // Every item should set its own LayoutDirection, or eliding fails miserably p->setLayoutDirection(text.isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight); QRect textRect = QRect(textMargin, 0, static_cast<int>(boundingRect().width() - 2 * textMargin), scene->itemHeight()); if (mMonthItem->monthScene()->monthView()->preferences()->enableMonthItemIcons()) { const QVector<QPixmap> icons = mMonthItem->icons(); int iconWidths = 0; for (const QPixmap &icon : icons) { iconWidths += icon.width(); } if (!icons.isEmpty()) { // add some margin between the icons and the text iconWidths += textMargin / 2; } int textWidth = p->fontMetrics().size(0, text).width(); if (textWidth + iconWidths > textRect.width()) { textWidth = textRect.width() - iconWidths; text = p->fontMetrics().elidedText(text, Qt::ElideRight, textWidth); } int curXPos = textRect.left(); if (alignFlag & Qt::AlignRight) { curXPos += textRect.width() - textWidth - iconWidths; } else if (alignFlag & Qt::AlignHCenter) { curXPos += (textRect.width() - textWidth - iconWidths) / 2; } alignFlag &= ~(Qt::AlignRight | Qt::AlignCenter); alignFlag |= Qt::AlignLeft; // update the rect, where the text will be displayed textRect.setLeft(curXPos + iconWidths); // assume that all pixmaps have the same height int pixYPos = icons.isEmpty() ? 0 : (textRect.height() - icons.at(0).height()) / 2; for (const QPixmap &icon : qAsConst(icons)) { p->drawPixmap(curXPos, pixYPos, icon); curXPos += icon.width(); } p->drawText(textRect, alignFlag | Qt::AlignVCenter, text); } else { text = p->fontMetrics().elidedText(text, Qt::ElideRight, textRect.width()); p->drawText(textRect, alignFlag, text); } } void MonthGraphicsItem::setStartDate(const QDate &date) { mStartDate = date; } QDate MonthGraphicsItem::endDate() const { return startDate().addDays(daySpan()); } QDate MonthGraphicsItem::startDate() const { return mStartDate; } void MonthGraphicsItem::setDaySpan(int span) { mDaySpan = span; } int MonthGraphicsItem::daySpan() const { return mDaySpan; } void MonthGraphicsItem::updateGeometry() { MonthCell *cell = mMonthItem->monthScene()->mMonthCellMap.value(startDate()); // If the item is moving and this one is moved outside the view, // cell will be null if (mMonthItem->isMoving() && !cell) { hide(); return; } Q_ASSERT(cell); prepareGeometryChange(); int beginX = 1 + mMonthItem->monthScene()->cellHorizontalPos(cell); int beginY = 1 + cell->topMargin() + mMonthItem->monthScene()->cellVerticalPos(cell); beginY += mMonthItem->position() *mMonthItem->monthScene()->itemHeightIncludingSpacing() -mMonthItem->monthScene()->startHeight() *mMonthItem->monthScene()->itemHeightIncludingSpacing(); // scrolling setPos(beginX, beginY); if (mMonthItem->position() < mMonthItem->monthScene()->startHeight() || mMonthItem->position() - mMonthItem->monthScene()->startHeight() >= mMonthItem->monthScene()->maxRowCount()) { hide(); } else { show(); update(); } } QString MonthGraphicsItem::getToolTip() const { return mMonthItem->toolTipText(mStartDate); } 07070100000032000081A40000000200000002000000015F0BF3C90000144A000000000000000000000000000000000000004D00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/month/monthgraphicsitems.h/* Copyright (c) 2008 Bruno Virlet <bruno.virlet@gmail.com> Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_MONTHGRAPHICSITEMS_H #define EVENTVIEWS_MONTHGRAPHICSITEMS_H #include <QDate> #include <QGraphicsItem> namespace EventViews { class MonthItem; /** * Graphics items which indicates that the view can be scrolled to display * more events. */ class ScrollIndicator : public QGraphicsItem { public: enum ArrowDirection { UpArrow, DownArrow }; explicit ScrollIndicator(ArrowDirection direction); Q_REQUIRED_RESULT QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; Q_REQUIRED_RESULT ArrowDirection direction() const { return mDirection; } private: ArrowDirection mDirection; static const int mWidth = 30; static const int mHeight = 10; }; /** * Keeps information about a month cell. */ class MonthCell { public: MonthCell(int id, const QDate &date, QGraphicsScene *scene); ~MonthCell(); /** This is used to get the height of the minimum height (vertical position) in the month cells. */ QList<MonthItem *> mMonthItemList; QHash<int, MonthItem *> mHeightHash; int firstFreeSpace(); void addMonthItem(MonthItem *manager, int height); int id() const { return mId; } QDate date() const { return mDate; } int x() const { return mId % 7; } int y() const { return mId / 7; } static int topMargin(); // returns true if the cell contains events below the height @p height bool hasEventBelow(int height); // TODO : move this to a new GUI class (monthcell could be GraphicsItems) ScrollIndicator *upArrow() const { return mUpArrow; } ScrollIndicator *downArrow() const { return mDownArrow; } private: int mId; QDate mDate; QGraphicsScene *mScene = nullptr; ScrollIndicator *mUpArrow = nullptr; ScrollIndicator *mDownArrow = nullptr; }; /** * A MonthGraphicsItem representing a part of an event. There should be * one part per row = week */ class MonthGraphicsItem : public QObject, public QGraphicsItem { Q_OBJECT Q_INTERFACES(QGraphicsItem) public: typedef QList<MonthGraphicsItem *> List; explicit MonthGraphicsItem(MonthItem *manager); ~MonthGraphicsItem(); /** Change QGraphicsItem pos and boundingRect in the scene according to the incidence start and end date. */ void updateGeometry(); /** Returns the associated MonthItem. */ MonthItem *monthItem() const { return mMonthItem; } /** Returns the starting date of this item. */ QDate startDate() const; /** Returns the number of day this item spans on minus one to be compatible with QDate::addDays(). */ int daySpan() const; /** Computed from startDate() and daySpan(). */ QDate endDate() const; void setStartDate(const QDate &d); void setDaySpan(int span); /** Returns true if this item is currently being moved (ie. the associated MonthItem is being moved). */ bool isMoving() const; /** Returns true if this item is currently being resized (ie. the associated MonthItem is being moved). */ bool isResizing() const; /** Returns true if this MonthGraphicsItem is the first one of the MonthItem ones. */ bool isBeginItem() const; /** Returns true if this MonthGraphicsItem is the last one of the MonthItem ones. */ bool isEndItem() const; /** Reimplemented from QGraphicsItem */ QRectF boundingRect() const override; void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override; QPainterPath shape() const override; QString getToolTip() const; private: // Shape of the item, see shape() QPainterPath widgetPath(bool border) const; // See startDate() QDate mStartDate; // See daySpan() int mDaySpan; // The current item is part of a MonthItem MonthItem *mMonthItem = nullptr; }; } #endif 07070100000033000081A40000000200000002000000015F0BF3C9000062A2000000000000000000000000000000000000004600000000eventviews-VERSIONgit.20200713T074025~752bb43/src/month/monthitem.cpp/* Copyright (c) 2008 Bruno Virlet <bruno.virlet@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "monthitem.h" #include "helper.h" #include "monthgraphicsitems.h" #include "monthscene.h" #include "monthview.h" #include "prefs.h" #include "prefs_base.h" // Ugly, but needed for the Enums #include <Akonadi/Calendar/IncidenceChanger> #include <CalendarSupport/KCalPrefs> #include <CalendarSupport/Utils> #include <KCalUtils/IncidenceFormatter> #include <KCalUtils/RecurrenceActions> #include <KMessageBox> #include "calendarview_debug.h" using namespace EventViews; using namespace KCalendarCore; MonthItem::MonthItem(MonthScene *monthScene) : mMonthScene(monthScene) , mSelected(false) , mMoving(false) , mResizing(false) { } MonthItem::~MonthItem() { deleteAll(); } void MonthItem::deleteAll() { qDeleteAll(mMonthGraphicsItemList); mMonthGraphicsItemList.clear(); } QWidget *MonthItem::parentWidget() const { return mMonthScene ? mMonthScene->monthView() : nullptr; } void MonthItem::updateMonthGraphicsItems() { // Remove all items qDeleteAll(mMonthGraphicsItemList); mMonthGraphicsItemList.clear(); const QDate monthStartDate = startDate(); const QDate monthEndDate = endDate(); // For each row of the month view, create an item to build the whole // MonthItem's MonthGraphicsItems. for (QDate d = mMonthScene->mMonthView->actualStartDateTime().date(); d < mMonthScene->mMonthView->actualEndDateTime().date(); d = d.addDays(7)) { QDate end = d.addDays(6); int span; QDate start; if (monthStartDate <= d && monthEndDate >= end) { // MonthItem takes the whole line span = 6; start = d; } else if (monthStartDate >= d && monthEndDate <= end) { // starts and ends on this line start = monthStartDate; span = daySpan(); } else if (d <= monthEndDate && monthEndDate <= end) { // MonthItem ends on this line span = mMonthScene->getLeftSpan(monthEndDate); start = d; } else if (d <= monthStartDate && monthStartDate <= end) { // MonthItem begins on this line span = mMonthScene->getRightSpan(monthStartDate); start = monthStartDate; } else { // MonthItem is not on the line continue; } // A new item needs to be created MonthGraphicsItem *newItem = new MonthGraphicsItem(this); mMonthGraphicsItemList << newItem; newItem->setStartDate(start); newItem->setDaySpan(span); } if (isMoving() || isResizing()) { setZValue(100); } else { setZValue(0); } } void MonthItem::beginResize() { mOverrideDaySpan = daySpan(); mOverrideStartDate = startDate(); mResizing = true; setZValue(100); } void MonthItem::endResize() { setZValue(0); mResizing = false; // startDate() and daySpan() return real values again if (mOverrideStartDate != startDate() || mOverrideDaySpan != daySpan()) { finalizeResize(mOverrideStartDate, mOverrideStartDate.addDays(mOverrideDaySpan)); } } void MonthItem::beginMove() { mOverrideDaySpan = daySpan(); mOverrideStartDate = startDate(); mMoving = true; setZValue(100); } void MonthItem::endMove() { setZValue(0); mMoving = false; // startDate() and daySpan() return real values again if (mOverrideStartDate != startDate()) { finalizeMove(mOverrideStartDate); } } bool MonthItem::resizeBy(int offsetToPreviousDate) { bool ret = false; if (mMonthScene->resizeType() == MonthScene::ResizeLeft) { if (mOverrideDaySpan - offsetToPreviousDate >= 0) { mOverrideStartDate = mOverrideStartDate.addDays(offsetToPreviousDate); mOverrideDaySpan = mOverrideDaySpan - offsetToPreviousDate; ret = true; } } else if (mMonthScene->resizeType() == MonthScene::ResizeRight) { if (mOverrideDaySpan + offsetToPreviousDate >= 0) { mOverrideDaySpan = mOverrideDaySpan + offsetToPreviousDate; ret = true; } } if (ret) { updateMonthGraphicsItems(); } return ret; } void MonthItem::moveBy(int offsetToPreviousDate) { mOverrideStartDate = mOverrideStartDate.addDays(offsetToPreviousDate); updateMonthGraphicsItems(); } void MonthItem::updateGeometry() { for (MonthGraphicsItem *item : qAsConst(mMonthGraphicsItemList)) { item->updateGeometry(); } } void MonthItem::setZValue(qreal z) { for (MonthGraphicsItem *item : qAsConst(mMonthGraphicsItemList)) { item->setZValue(z); } } QDate MonthItem::startDate() const { if (isMoving() || isResizing()) { return mOverrideStartDate; } return realStartDate(); } QDate MonthItem::endDate() const { if (isMoving() || isResizing()) { return mOverrideStartDate.addDays(mOverrideDaySpan); } return realEndDate(); } int MonthItem::daySpan() const { if (isMoving() || isResizing()) { return mOverrideDaySpan; } #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) QDateTime start(startDate()); QDateTime end(endDate()); #else QDateTime start(startDate().startOfDay()); QDateTime end(endDate().startOfDay()); #endif if (start.isValid() && end.isValid()) { return start.daysTo(end); } return 0; } bool MonthItem::greaterThan(const MonthItem *e1, const MonthItem *e2) { const QDate leftStartDate = e1->startDate(); const QDate rightStartDate = e2->startDate(); if (!leftStartDate.isValid() || !rightStartDate.isValid()) { return false; } if (leftStartDate == rightStartDate) { const int leftDaySpan = e1->daySpan(); const int rightDaySpan = e2->daySpan(); if (leftDaySpan == rightDaySpan) { if (e1->allDay() && !e2->allDay()) { return true; } if (!e1->allDay() && e2->allDay()) { return false; } return e1->greaterThanFallback(e2); } else { return leftDaySpan > rightDaySpan; } } return leftStartDate > rightStartDate; } bool MonthItem::greaterThanFallback(const MonthItem *other) const { const HolidayMonthItem *h = qobject_cast<const HolidayMonthItem *>(other); // If "other" is a holiday, display it first. return !h; } void MonthItem::updatePosition() { if (!startDate().isValid() || !endDate().isValid()) { return; } int firstFreeSpace = 0; for (QDate d = startDate(); d <= endDate(); d = d.addDays(1)) { MonthCell *cell = mMonthScene->mMonthCellMap.value(d); if (!cell) { continue; // cell can be null if the item begins outside the month } int firstFreeSpaceTmp = cell->firstFreeSpace(); if (firstFreeSpaceTmp > firstFreeSpace) { firstFreeSpace = firstFreeSpaceTmp; } } for (QDate d = startDate(); d <= endDate(); d = d.addDays(1)) { MonthCell *cell = mMonthScene->mMonthCellMap.value(d); if (!cell) { continue; } cell->addMonthItem(this, firstFreeSpace); } mPosition = firstFreeSpace; } QList<MonthGraphicsItem *> EventViews::MonthItem::monthGraphicsItems() const { return mMonthGraphicsItemList; } //----------------------------------------------------------------- // INCIDENCEMONTHITEM IncidenceMonthItem::IncidenceMonthItem(MonthScene *monthScene, const Akonadi::ETMCalendar::Ptr &calendar, const Akonadi::Item &aitem, const KCalendarCore::Incidence::Ptr &incidence, const QDate &recurStartDate) : MonthItem(monthScene) , mCalendar(calendar) , mIncidence(incidence) , mAkonadiItemId(aitem.id()) { mIsEvent = CalendarSupport::hasEvent(aitem); mIsJournal = CalendarSupport::hasJournal(aitem); mIsTodo = CalendarSupport::hasTodo(aitem); KCalendarCore::Incidence::Ptr inc = mIncidence; if (inc->customProperty("KABC", "BIRTHDAY") == QLatin1String("YES") || inc->customProperty("KABC", "ANNIVERSARY") == QLatin1String("YES")) { const int years = EventViews::yearDiff(inc->dtStart().date(), recurStartDate); if (years > 0) { inc = KCalendarCore::Incidence::Ptr(inc->clone()); inc->setReadOnly(false); inc->setDescription(i18np("%2 1 year", "%2 %1 years", years, i18n("Age:"))); inc->setReadOnly(true); mIncidence = inc; } } connect(monthScene, &MonthScene::incidenceSelected, this, &IncidenceMonthItem::updateSelection); // first set to 0, because it's used in startDate() mRecurDayOffset = 0; if ((mIncidence->recurs() || mIncidence->recurrenceId().isValid()) && startDate().isValid() && recurStartDate.isValid()) { mRecurDayOffset = startDate().daysTo(recurStartDate); } } IncidenceMonthItem::~IncidenceMonthItem() { } bool IncidenceMonthItem::greaterThanFallback(const MonthItem *other) const { const IncidenceMonthItem *o = qobject_cast<const IncidenceMonthItem *>(other); if (!o) { return MonthItem::greaterThanFallback(other); } if (allDay() != o->allDay()) { return allDay(); } const KCalendarCore::Incidence::Ptr otherIncidence = o->mIncidence; if (mIncidence->dtStart().time() != otherIncidence->dtStart().time()) { return mIncidence->dtStart().time() < otherIncidence->dtStart().time(); } // as a last resort, compare uids return mIncidence->uid() < otherIncidence->uid(); } QDate IncidenceMonthItem::realStartDate() const { if (!mIncidence) { return QDate(); } const QDateTime dt = mIncidence->dateTime(Incidence::RoleDisplayStart); const QDate start = dt.toLocalTime().date(); return start.addDays(mRecurDayOffset); } QDate IncidenceMonthItem::realEndDate() const { if (!mIncidence) { return QDate(); } const QDateTime dt = mIncidence->dateTime(KCalendarCore::Incidence::RoleDisplayEnd); const QDate end = dt.toLocalTime().date(); return end.addDays(mRecurDayOffset); } bool IncidenceMonthItem::allDay() const { return mIncidence->allDay(); } bool IncidenceMonthItem::isMoveable() const { return monthScene()->mMonthView->calendar()->hasRight(akonadiItem(), Akonadi::Collection::CanChangeItem); } bool IncidenceMonthItem::isResizable() const { return mIsEvent && monthScene()->mMonthView->calendar()->hasRight( akonadiItem(), Akonadi::Collection::CanChangeItem); } void IncidenceMonthItem::finalizeMove(const QDate &newStartDate) { Q_ASSERT(isMoveable()); if (startDate().isValid() && newStartDate.isValid()) { updateDates(startDate().daysTo(newStartDate), startDate().daysTo(newStartDate)); } } void IncidenceMonthItem::finalizeResize(const QDate &newStartDate, const QDate &newEndDate) { Q_ASSERT(isResizable()); if (startDate().isValid() && endDate().isValid() && newStartDate.isValid() && newEndDate.isValid()) { updateDates(startDate().daysTo(newStartDate), endDate().daysTo(newEndDate)); } } void IncidenceMonthItem::updateDates(int startOffset, int endOffset) { Akonadi::IncidenceChanger *changer = monthScene()->incidenceChanger(); if (!changer || (startOffset == 0 && endOffset == 0)) { qCDebug(CALENDARVIEW_LOG) << changer << startOffset << endOffset; return; } Akonadi::Item item = akonadiItem(); item.setPayload(mIncidence); if (mIncidence->recurs()) { const int res = monthScene()->mMonthView->showMoveRecurDialog(mIncidence, startDate()); switch (res) { case KCalUtils::RecurrenceActions::AllOccurrences: { // All occurrences KCalendarCore::Incidence::Ptr oldIncidence(mIncidence->clone()); setNewDates(mIncidence, startOffset, endOffset); changer->modifyIncidence(item, oldIncidence); break; } case KCalUtils::RecurrenceActions::SelectedOccurrence: // Just this occurrence case KCalUtils::RecurrenceActions::FutureOccurrences: { // All future occurrences const bool thisAndFuture = (res == KCalUtils::RecurrenceActions::FutureOccurrences); QDateTime occurrenceDate(mIncidence->dtStart()); occurrenceDate.setDate(startDate()); KCalendarCore::Incidence::Ptr newIncidence(KCalendarCore::Calendar::createException( mIncidence, occurrenceDate, thisAndFuture)); if (newIncidence) { changer->startAtomicOperation(i18n("Move occurrence(s)")); setNewDates(newIncidence, startOffset, endOffset); changer->createIncidence(newIncidence, item.parentCollection(), parentWidget()); changer->endAtomicOperation(); } else { KMessageBox::sorry( parentWidget(), i18n("Unable to add the exception item to the calendar. " "No change will be done."), i18n("Error Occurred")); } break; } } } else { // Doesn't recur KCalendarCore::Incidence::Ptr oldIncidence(mIncidence->clone()); setNewDates(mIncidence, startOffset, endOffset); changer->modifyIncidence(item, oldIncidence); } } void IncidenceMonthItem::updateSelection(const Akonadi::Item &incidence, const QDate &date) { Q_UNUSED(date); setSelected(incidence == akonadiItem()); } QString IncidenceMonthItem::text(bool end) const { QString ret = mIncidence->summary(); if (!allDay() && !mIsJournal && monthScene()->monthView()->preferences()->showTimeInMonthView()) { // Prepend the time str to the text QString timeStr; if (mIsTodo) { KCalendarCore::Todo::Ptr todo = mIncidence.staticCast<Todo>(); timeStr = QLocale().toString(todo->dtDue().toLocalTime().time(), QLocale::ShortFormat); } else { if (!end) { QTime time; if (mIncidence->recurs()) { const auto start = mIncidence->dtStart().addDays(mRecurDayOffset).addSecs(-1); time = mIncidence->recurrence()->getNextDateTime(start).toLocalTime().time(); } else { time = mIncidence->dtStart().toLocalTime().time(); } timeStr = QLocale().toString(time, QLocale::ShortFormat); } else { KCalendarCore::Event::Ptr event = mIncidence.staticCast<Event>(); timeStr = QLocale().toString( event->dtEnd().toLocalTime().time(), QLocale::ShortFormat); } } if (!timeStr.isEmpty()) { if (!end) { ret = timeStr + QLatin1Char(' ') + ret; } else { ret = ret + QLatin1Char(' ') + timeStr; } } } return ret; } QString IncidenceMonthItem::toolTipText(const QDate &date) const { return KCalUtils::IncidenceFormatter::toolTipStr( CalendarSupport::displayName(mCalendar.data(), akonadiItem().parentCollection()), mIncidence, date, true); } QVector<QPixmap> IncidenceMonthItem::icons() const { QVector<QPixmap> ret; if (!mIncidence) { return ret; } bool specialEvent = false; Akonadi::Item item = akonadiItem(); const QSet<EventView::ItemIcon> icons = monthScene()->monthView()->preferences()->monthViewIcons(); QString customIconName; if (icons.contains(EventViews::EventView::CalendarCustomIcon)) { const QString iconName = monthScene()->monthView()->iconForItem(item); if (!iconName.isEmpty() && iconName != QLatin1String("view-calendar") && iconName != QLatin1String("office-calendar")) { customIconName = iconName; ret << QPixmap(cachedSmallIcon(iconName)); } } if (mIsEvent) { if (mIncidence->customProperty("KABC", "ANNIVERSARY") == QLatin1String("YES")) { specialEvent = true; ret << monthScene()->anniversaryPixmap(); } else if (mIncidence->customProperty("KABC", "BIRTHDAY") == QLatin1String("YES")) { specialEvent = true; // Disabling birthday icon because it's the birthday agent's icon // and we allow to display the agent's icon now. //ret << monthScene()->birthdayPixmap(); } // smartins: Disabling the event Pixmap because: // 1. Save precious space so we can read the event's title better. // 2. We don't need a pixmap to tell us an item is an event we // only need one to tell us it's not, as month view was designed for events. // 3. If only to-dos and journals have a pixmap they will be distinguished // from event's much easier. // ret << monthScene()->eventPixmap(); } else if ((mIsTodo || mIsJournal) && icons.contains(mIsTodo ? EventView::TaskIcon : EventView::JournalIcon)) { QDateTime occurrenceDateTime = mIncidence->dateTime(Incidence::RoleRecurrenceStart); occurrenceDateTime.setDate(realStartDate()); const QString incidenceIconName = mIncidence->iconName(occurrenceDateTime); if (customIconName != incidenceIconName) { ret << QPixmap(cachedSmallIcon(incidenceIconName)); } } if (icons.contains(EventView::ReadOnlyIcon) && !monthScene()->mMonthView->calendar()->hasRight(item, Akonadi::Collection::CanChangeItem) && !specialEvent) { ret << monthScene()->readonlyPixmap(); } /* sorry, this looks too cluttered. disable until we can make something prettier; no idea at this time -- allen */ if (icons.contains(EventView::ReminderIcon) && mIncidence->hasEnabledAlarms() && !specialEvent) { ret << monthScene()->alarmPixmap(); } if (icons.contains(EventView::RecurringIcon) && mIncidence->recurs() && !specialEvent) { ret << monthScene()->recurPixmap(); } //TODO: check what to do with Reply return ret; } QColor IncidenceMonthItem::catColor() const { Q_ASSERT(mIncidence); const auto &prefs = monthScene()->monthView()->preferences(); const auto &categories = mIncidence->categories(); if (categories.isEmpty() || !CalendarSupport::KCalPrefs::instance()->hasCategoryColor(categories.first())) { const auto &colorPreference = prefs->monthViewColors(); if (colorPreference == PrefsBase::CategoryOnly) { return CalendarSupport::KCalPrefs::instance()->unsetCategoryColor(); } return EventViews::resourceColor(akonadiItem(), prefs); } return CalendarSupport::KCalPrefs::instance()->categoryColor(categories.first()); } QColor IncidenceMonthItem::bgColor() const { const auto &prefs = monthScene()->monthView()->preferences(); if (!prefs->todosUseCategoryColors() && mIsTodo) { Todo::Ptr todo = CalendarSupport::todo(akonadiItem()); Q_ASSERT(todo); if (todo) { // this is dtDue if there's no dtRecurrence const auto dtRecurrence = todo->dtRecurrence().toLocalTime().date(); const auto today = QDate::currentDate(); if (startDate() >= dtRecurrence) { if (todo->isOverdue() && today > startDate()) { return prefs->todoOverdueColor(); } if (today == startDate() && !todo->isCompleted()) { return prefs->todoDueTodayColor(); } } } } const auto &colorPreference = prefs->monthViewColors(); const auto bgDisplaysResource = colorPreference == PrefsBase::MonthItemResourceInsideCategoryOutside || colorPreference == PrefsBase::MonthItemResourceOnly; return bgDisplaysResource ? EventViews::resourceColor(akonadiItem(), prefs) : catColor(); } QColor IncidenceMonthItem::frameColor() const { const auto &prefs = monthScene()->monthView()->preferences(); const auto frameDisplaysResource = (prefs->monthViewColors() == PrefsBase::MonthItemResourceOnly || prefs->monthViewColors() == PrefsBase::MonthItemCategoryInsideResourceOutside); const auto frameColor = frameDisplaysResource ? EventViews::resourceColor(akonadiItem(), prefs) : catColor(); return EventView::itemFrameColor(frameColor, selected()); } Akonadi::Item IncidenceMonthItem::akonadiItem() const { if (mIncidence) { return monthScene()->mMonthView->calendar()->item(mIncidence); } else { return Akonadi::Item(); } } KCalendarCore::Incidence::Ptr IncidenceMonthItem::incidence() const { return mIncidence; } Akonadi::Item::Id IncidenceMonthItem::akonadiItemId() const { return mAkonadiItemId; } void IncidenceMonthItem::setNewDates(const KCalendarCore::Incidence::Ptr &incidence, int startOffset, int endOffset) { if (mIsTodo) { // For to-dos endOffset is ignored because it will always be == to startOffset because we only // support moving to-dos, not resizing them. There are no multi-day to-dos. // Lets just call it offset to reduce confusion. const int offset = startOffset; KCalendarCore::Todo::Ptr todo = incidence.staticCast<Todo>(); QDateTime due = todo->dtDue(); QDateTime start = todo->dtStart(); if (due.isValid()) { // Due has priority over start. // We will only move the due date, unlike events where we move both. due = due.addDays(offset); todo->setDtDue(due); if (start.isValid() && start > due) { // Start can't be bigger than due. todo->setDtStart(due); } } else if (start.isValid()) { // So we're displaying a to-do that doesn't have due date, only start... start = start.addDays(offset); todo->setDtStart(start); } else { // This never happens qCWarning(CALENDARVIEW_LOG) << "Move what? uid:" << todo->uid() << "; summary=" << todo->summary(); } } else { incidence->setDtStart(incidence->dtStart().addDays(startOffset)); if (mIsEvent) { KCalendarCore::Event::Ptr event = incidence.staticCast<Event>(); event->setDtEnd(event->dtEnd().addDays(endOffset)); } } } //----------------------------------------------------------------- // HOLIDAYMONTHITEM HolidayMonthItem::HolidayMonthItem(MonthScene *monthScene, const QDate &date, const QString &name) : MonthItem(monthScene) , mDate(date) , mName(name) { } HolidayMonthItem::~HolidayMonthItem() { } bool HolidayMonthItem::greaterThanFallback(const MonthItem *other) const { const HolidayMonthItem *o = qobject_cast<const HolidayMonthItem *>(other); if (o) { return MonthItem::greaterThanFallback(other); } // always put holidays on top return false; } void HolidayMonthItem::finalizeMove(const QDate &newStartDate) { Q_UNUSED(newStartDate); Q_ASSERT(false); } void HolidayMonthItem::finalizeResize(const QDate &newStartDate, const QDate &newEndDate) { Q_UNUSED(newStartDate); Q_UNUSED(newEndDate); Q_ASSERT(false); } QVector<QPixmap> HolidayMonthItem::icons() const { QVector<QPixmap> ret; ret << monthScene()->holidayPixmap(); return ret; } QColor HolidayMonthItem::bgColor() const { // FIXME: Currently, only this value is settable in the options. // There is a monthHolidaysBackgroundColor() option too. Maybe it would be // wise to merge those two. return monthScene()->monthView()->preferences()->agendaHolidaysBackgroundColor(); } QColor HolidayMonthItem::frameColor() const { return Qt::black; } 07070100000034000081A40000000200000002000000015F0BF3C90000269D000000000000000000000000000000000000004400000000eventviews-VERSIONgit.20200713T074025~752bb43/src/month/monthitem.h/* Copyright (c) 2008 Bruno Virlet <bruno.virlet@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_MONTHITEM_H #define EVENTVIEWS_MONTHITEM_H #include "eventviews_export.h" #include <Item> #include <KCalendarCore/Incidence> #include <Akonadi/Calendar/ETMCalendar> #include <QDate> #include <QObject> namespace EventViews { class MonthGraphicsItem; class MonthScene; /** * A month item manages different MonthGraphicsItems. */ class EVENTVIEWS_EXPORT MonthItem : public QObject { Q_OBJECT public: explicit MonthItem(MonthScene *monthWidget); ~MonthItem() override; QWidget *parentWidget() const; /** Compares two events The month view displays a list of items. When loading (which occurs each time there is a change), the items are sorted : - smallest date - bigger span - floating - finally, time in the day */ static bool greaterThan(const MonthItem *e1, const MonthItem *e2); /** Compare this event with a second one, if the former function is not able to sort them. */ virtual bool greaterThanFallback(const MonthItem *other) const; /** The start date of the incidence, generally realStartDate. But it reflect changes, even during move. */ Q_REQUIRED_RESULT QDate startDate() const; /** The end date of the incidence, generally realEndDate. But it reflect changes, even during move. */ Q_REQUIRED_RESULT QDate endDate() const; /** The number of days this item spans. */ Q_REQUIRED_RESULT int daySpan() const; /** This is the real start date, usually the start date of the incidence. */ virtual QDate realStartDate() const = 0; /** This is the real end date, usually the end date of the incidence. */ virtual QDate realEndDate() const = 0; /** True if this item last all the day. */ virtual bool allDay() const = 0; /** Updates geometry of all MonthGraphicsItems. */ void updateGeometry(); /** Find the lowest possible position for this item. The position of an item in a cell is it's vertical position. This is used to avoid overlapping of items. An item keeps the same position in every cell it crosses. The position is measured from top to bottom. */ void updatePosition(); /** Returns true if this item is selected. */ Q_REQUIRED_RESULT bool selected() const { return mSelected; } /** Returns the position of the item ( > 0 ). */ Q_REQUIRED_RESULT int position() const { return mPosition; } /** Returns the associated month scene to this item. */ MonthScene *monthScene() const { return mMonthScene; } /** Begin a move. */ void beginMove(); /** End a move. */ void endMove(); /** Begin a resize. */ void beginResize(); /** End a resize. */ void endResize(); /** Called during move to move the item a bit, relative to the previous move step. */ void moveBy(int offsetFromPreviousDate); /** Called during resize to resize the item a bit, relative to the previous resize step. */ bool resizeBy(int offsetFromPreviousDate); /** Returns true if the item is being moved. */ Q_REQUIRED_RESULT bool isMoving() const { return mMoving; } /** Returns true if the item is being resized. */ Q_REQUIRED_RESULT bool isResizing() const { return mResizing; } /** Returns true if the item can be moved. */ virtual bool isMoveable() const = 0; /** Returns true if the item can be resized. */ virtual bool isResizable() const = 0; /** Deletes all MonthGraphicsItem this item handles. Clear the list. */ void deleteAll(); /** Update the monthgraphicsitems This basically deletes and rebuild all the MonthGraphicsItems but tries to do it wisely: - If there is a moving item, it won't be deleted because then the new item won't receive anymore the MouseMove events. - If there is an item on a line where the new state needs an item, it is used and not deleted. This will avoid flickers. */ void updateMonthGraphicsItems(); /** Sets the selection state of this item. */ void setSelected(bool selected) { mSelected = selected; } // METHODS NEEDED TO PAINT ITEMS /** Returns the text to draw in an item. @param end True if the text at the end of an item should be returned. */ virtual QString text(bool end) const = 0; /** Returns the text for the tooltip of the item */ virtual QString toolTipText(const QDate &date) const = 0; /** Returns the background color of the item. */ virtual Q_REQUIRED_RESULT QColor bgColor() const = 0; /** Returns the frame color of the item. */ virtual Q_REQUIRED_RESULT QColor frameColor() const = 0; /** Returns a list of pixmaps to draw next to the items. */ virtual QVector<QPixmap> icons() const = 0; QList<MonthGraphicsItem *> monthGraphicsItems() const; protected: /** Called after a move operation. */ virtual void finalizeMove(const QDate &newStartDate) = 0; /** Called after a resize operation. */ virtual void finalizeResize(const QDate &newStartDate, const QDate &newEndDate) = 0; private: /** Sets the value of all MonthGraphicsItem to @param z. */ void setZValue(qreal z); QList<MonthGraphicsItem *> mMonthGraphicsItemList; MonthScene *mMonthScene = nullptr; bool mSelected; bool mMoving; // during move bool mResizing; // during resize QDate mOverrideStartDate; int mOverrideDaySpan; int mPosition; }; class EVENTVIEWS_EXPORT IncidenceMonthItem : public MonthItem { Q_OBJECT public: IncidenceMonthItem(MonthScene *monthScene, const Akonadi::ETMCalendar::Ptr &calendar, const Akonadi::Item &item, const KCalendarCore::Incidence::Ptr &incidence, const QDate &recurStartDate = QDate()); ~IncidenceMonthItem() override; KCalendarCore::Incidence::Ptr incidence() const; Akonadi::Item akonadiItem() const; Akonadi::Item::Id akonadiItemId() const; bool greaterThanFallback(const MonthItem *other) const override; QDate realStartDate() const override; QDate realEndDate() const override; bool allDay() const override; bool isMoveable() const override; bool isResizable() const override; QString text(bool end) const override; QString toolTipText(const QDate &date) const override; QColor bgColor() const override; QColor frameColor() const override; QVector<QPixmap> icons() const override; protected: void finalizeMove(const QDate &newStartDate) override; void finalizeResize(const QDate &newStartDate, const QDate &newEndDate) override; protected Q_SLOTS: /** Update the selected state of this item. If will be selected if incidence is the incidence managed by this item. Else it will be deselected. */ void updateSelection(const Akonadi::Item &incidence, const QDate &date); private: void updateDates(int startOffset, int endOffset); void setNewDates(const KCalendarCore::Incidence::Ptr &incidence, int startOffset, int endOffset); /** Returns the category color for this incidence. */ QColor catColor() const; Akonadi::ETMCalendar::Ptr mCalendar; KCalendarCore::Incidence::Ptr mIncidence; Akonadi::Item::Id mAkonadiItemId; int mRecurDayOffset; bool mIsEvent, mIsTodo, mIsJournal; }; class EVENTVIEWS_EXPORT HolidayMonthItem : public MonthItem { Q_OBJECT public: HolidayMonthItem(MonthScene *monthScene, const QDate &date, const QString &name); ~HolidayMonthItem() override; bool greaterThanFallback(const MonthItem *other) const override; QDate realStartDate() const override { return mDate; } QDate realEndDate() const override { return mDate; } bool allDay() const override { return true; } bool isMoveable() const override { return false; } bool isResizable() const override { return false; } QString text(bool end) const override { Q_UNUSED(end); return mName; } QString toolTipText(const QDate &) const override { return mName; } QColor bgColor() const override; QColor frameColor() const override; QVector<QPixmap> icons() const override; protected: void finalizeMove(const QDate &newStartDate) override; void finalizeResize(const QDate &newStartDate, const QDate &newEndDate) override; private: QDate mDate; QString mName; }; } #endif 07070100000035000081A40000000200000002000000015F0BF3C90000610C000000000000000000000000000000000000004700000000eventviews-VERSIONgit.20200713T074025~752bb43/src/month/monthscene.cpp/* Copyright (c) 2008 Bruno Virlet <bruno.virlet@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "monthscene.h" #include "monthgraphicsitems.h" #include "monthitem.h" #include "monthview.h" #include "helper.h" #include "prefs.h" #include <CalendarSupport/Utils> #include <QGraphicsSceneMouseEvent> #include <QResizeEvent> #include <KLocalizedString> #include <QToolTip> #include <QIcon> static const int AUTO_REPEAT_DELAY = 600; using namespace EventViews; MonthScene::MonthScene(MonthView *parent) : QGraphicsScene(parent) , mMonthView(parent) , mInitialized(false) , mClickedItem(nullptr) , mActionItem(nullptr) , mActionInitiated(false) , mSelectedItem(nullptr) , mStartCell(nullptr) , mPreviousCell(nullptr) , mActionType(None) , mStartHeight(0) , mCurrentIndicator(nullptr) { mBirthdayPixmap = QIcon::fromTheme(QStringLiteral("view-calendar-birthday")).pixmap(16, 16); mAnniversaryPixmap = QIcon::fromTheme(QStringLiteral("view-calendar-wedding-anniversary")).pixmap(16, 16); mAlarmPixmap = QIcon::fromTheme(QStringLiteral("appointment-reminder")).pixmap(16, 16); mRecurPixmap = QIcon::fromTheme(QStringLiteral("appointment-recurring")).pixmap(16, 16); mReadonlyPixmap = QIcon::fromTheme(QStringLiteral("object-locked")).pixmap(16, 16); mReplyPixmap = QIcon::fromTheme(QStringLiteral("mail-reply-sender")).pixmap(16, 16); mHolidayPixmap = QIcon::fromTheme(QStringLiteral("view-calendar-holiday")).pixmap(16, 16); setSceneRect(0, 0, parent->width(), parent->height()); } MonthScene::~MonthScene() { qDeleteAll(mMonthCellMap); qDeleteAll(mManagerList); } MonthCell *MonthScene::selectedCell() const { return mMonthCellMap.value(mSelectedCellDate); } MonthCell *MonthScene::previousCell() const { return mPreviousCell; } int MonthScene::getRightSpan(const QDate &date) const { MonthCell *cell = mMonthCellMap.value(date); if (!cell) { return 0; } return 7 - cell->x() - 1; } int MonthScene::getLeftSpan(const QDate &date) const { MonthCell *cell = mMonthCellMap.value(date); if (!cell) { return 0; } return cell->x(); } int MonthScene::maxRowCount() { return (rowHeight() - MonthCell::topMargin()) / itemHeightIncludingSpacing(); } int MonthScene::itemHeightIncludingSpacing() { return MonthCell::topMargin() + 2; } int MonthScene::itemHeight() { return MonthCell::topMargin(); } MonthCell *MonthScene::firstCellForMonthItem(MonthItem *manager) { for (QDate d = manager->startDate(); d <= manager->endDate(); d = d.addDays(1)) { MonthCell *monthCell = mMonthCellMap.value(d); if (monthCell) { return monthCell; } } return nullptr; } void MonthScene::updateGeometry() { for (MonthItem *manager : qAsConst(mManagerList)) { manager->updateGeometry(); } } int MonthScene::availableWidth() const { return static_cast<int>(sceneRect().width()); } int MonthScene::availableHeight() const { return static_cast<int>(sceneRect().height() - headerHeight()); } int MonthScene::columnWidth() const { return static_cast<int>((availableWidth() - 1) / 7.); } int MonthScene::rowHeight() const { return static_cast<int>((availableHeight() - 1) / 6.); } int MonthScene::headerHeight() const { return 50; } int MonthScene::cellVerticalPos(const MonthCell *cell) const { return headerHeight() + cell->y() * rowHeight(); } int MonthScene::cellHorizontalPos(const MonthCell *cell) const { return cell->x() * columnWidth(); } int MonthScene::sceneYToMonthGridY(int yScene) { return yScene - headerHeight(); } int MonthScene::sceneXToMonthGridX(int xScene) { return xScene; } void MonthGraphicsView::drawBackground(QPainter *p, const QRectF &rect) { Q_ASSERT(mScene); PrefsPtr prefs = mScene->monthView()->preferences(); p->setFont(prefs->monthViewFont()); p->fillRect(rect, palette().color(QPalette::Window)); /* Headers */ QFont font = prefs->monthViewFont(); font.setBold(true); font.setPointSize(15); p->setFont(font); const int dayLabelsHeight = 20; p->drawText(QRect(0, 0, // top right static_cast<int>(mScene->sceneRect().width()), static_cast<int>(mScene->headerHeight() - dayLabelsHeight)), Qt::AlignCenter, mMonthView->averageDate().toString(QStringLiteral("MMMM yyyy"))); font.setPointSize(dayLabelsHeight - 10); p->setFont(font); const QDate start = mMonthView->actualStartDateTime().date(); const QDate end = mMonthView->actualEndDateTime().date(); for (QDate d = start; d <= start.addDays(6); d = d.addDays(1)) { MonthCell *cell = mScene->mMonthCellMap.value(d); if (!cell) { // This means drawBackground() is being called before reloadIncidences(). Can happen with some // themes. Bug #190191 return; } p->drawText(QRect(mScene->cellHorizontalPos(cell), mScene->cellVerticalPos(cell) - 15, mScene->columnWidth(), 15), Qt::AlignCenter, QLocale::system().dayName(d.dayOfWeek(), QLocale::LongFormat)); } /* Month grid */ int columnWidth = mScene->columnWidth(); int rowHeight = mScene->rowHeight(); const QList<QDate> workDays = CalendarSupport::workDays( mMonthView->actualStartDateTime().date(), mMonthView->actualEndDateTime().date()); for (QDate d = start; d <= end; d = d.addDays(1)) { if (!mScene->mMonthCellMap.contains(d)) { // This means drawBackground() is being called before reloadIncidences(). Can happen with some // themes. Bug #190191 return; } MonthCell *cell = mScene->mMonthCellMap[ d ]; QColor color; if (!mMonthView->preferences()->useSystemColor()) { if (workDays.contains(d)) { color = mMonthView->preferences()->monthGridWorkHoursBackgroundColor(); } else { color = mMonthView->preferences()->monthGridBackgroundColor(); } } else { if (workDays.contains(d)) { color = palette().color(QPalette::Base); } else { color = palette().color(QPalette::AlternateBase); } } const bool usingDark = EventViews::isColorDark(color); if (cell == mScene->selectedCell()) { color = (usingDark) ? color.lighter(150) : color.darker(115); } if (cell->date() == QDate::currentDate()) { color = (usingDark) ? color.lighter(200) : color.darker(140); } // Draw cell p->setPen(mMonthView->preferences()->monthGridBackgroundColor().darker(150)); p->setBrush(color); p->drawRect(QRect(mScene->cellHorizontalPos(cell), mScene->cellVerticalPos(cell), columnWidth, rowHeight)); if (mMonthView->isBusyDay(d)) { QColor busyColor = mMonthView->preferences()->viewBgBusyColor(); busyColor.setAlpha(EventViews::BUSY_BACKGROUND_ALPHA); p->setBrush(busyColor); p->drawRect(QRect(mScene->cellHorizontalPos(cell), mScene->cellVerticalPos(cell), columnWidth, rowHeight)); } // Draw cell header int cellHeaderX = mScene->cellHorizontalPos(cell) + 1; int cellHeaderY = mScene->cellVerticalPos(cell) + 1; int cellHeaderWidth = columnWidth - 2; int cellHeaderHeight = cell->topMargin() - 2; QLinearGradient bgGradient(QPointF(cellHeaderX, cellHeaderY), QPointF(cellHeaderX + cellHeaderWidth, cellHeaderY + cellHeaderHeight)); // Compute color of grid lines based on dark/lightness if (!usingDark) { p->setBrush(color.darker(110)); } else { p->setBrush(color.lighter(140)); } bgGradient.setColorAt(1, color); p->setPen(Qt::NoPen); p->drawRect(QRect(cellHeaderX, cellHeaderY, cellHeaderWidth, cellHeaderHeight)); } font = mMonthView->preferences()->monthViewFont(); font.setPixelSize(MonthCell::topMargin() - 4); p->setFont(font); QPen oldPen; if (!mMonthView->preferences()->useSystemColor()) { oldPen = mMonthView->preferences()->monthGridBackgroundColor().darker(150); } else { oldPen = palette().color(QPalette::WindowText).darker(150); } // Draw dates for (QDate d = mMonthView->actualStartDateTime().date(); d <= mMonthView->actualEndDateTime().date(); d = d.addDays(1)) { MonthCell *cell = mScene->mMonthCellMap.value(d); QFont font = p->font(); if (cell->date() == QDate::currentDate()) { font.setBold(true); } else { font.setBold(false); } p->setFont(font); if (d.month() == mMonthView->currentMonth()) { p->setPen(palette().color(QPalette::WindowText)); } else { p->setPen(oldPen); } /* Draw arrows if all items won't fit */ // Up arrow if first item is above cell top if (mScene->startHeight() != 0 && cell->hasEventBelow(mScene->startHeight())) { cell->upArrow()->setPos( mScene->cellHorizontalPos(cell) + columnWidth / 2, mScene->cellVerticalPos(cell) + cell->upArrow()->boundingRect().height() / 2 + 2); cell->upArrow()->show(); } else { cell->upArrow()->hide(); } // Down arrow if last item is below cell bottom if (!mScene->lastItemFit(cell)) { cell->downArrow()->setPos( mScene->cellHorizontalPos(cell) + columnWidth / 2, mScene->cellVerticalPos(cell) + rowHeight -cell->downArrow()->boundingRect().height() / 2 - 2); cell->downArrow()->show(); } else { cell->downArrow()->hide(); } QString dayText; // Prepend month name if d is the first or last day of month if (d.day() == 1 || // d is the first day of month d.addDays(1).day() == 1) { // d is the last day of month dayText = i18nc("'Month day' for month view cells", "%1 %2", QLocale::system().monthName(d.month(), QLocale::ShortFormat), d.day()); } else { dayText = QString::number(d.day()); } p->drawText(QRect(mScene->cellHorizontalPos(cell), // top right mScene->cellVerticalPos(cell), // of the cell mScene->columnWidth() - 2, cell->topMargin()), Qt::AlignRight, dayText); } // ... } void MonthScene::resetAll() { qDeleteAll(mMonthCellMap); mMonthCellMap.clear(); qDeleteAll(mManagerList); mManagerList.clear(); mSelectedItem = nullptr; mActionItem = nullptr; mClickedItem = nullptr; } Akonadi::IncidenceChanger *MonthScene::incidenceChanger() const { return mMonthView->changer(); } QDate MonthScene::firstDateOnRow(int row) const { return mMonthView->actualStartDateTime().date().addDays(7 * row); } bool MonthScene::lastItemFit(MonthCell *cell) { if (cell->firstFreeSpace() > maxRowCount() + startHeight()) { return false; } else { return true; } } int MonthScene::totalHeight() { int max = 0; for (QDate d = mMonthView->actualStartDateTime().date(); d <= mMonthView->actualEndDateTime().date(); d = d.addDays(1)) { int c = mMonthCellMap[ d ]->firstFreeSpace(); if (c > max) { max = c; } } return max; } void MonthScene::wheelEvent(QGraphicsSceneWheelEvent *event) { Q_UNUSED(event); // until we figure out what to do in here /* int numDegrees = -event->delta() / 8; int numSteps = numDegrees / 15; if (startHeight() + numSteps < 0) { numSteps = -startHeight(); } int cellHeight = 0; MonthCell *currentCell = getCellFromPos(event->scenePos()); if (currentCell) { cellHeight = currentCell->firstFreeSpace(); } if (cellHeight == 0) { // no items in this cell, there's no point to scroll return; } int newHeight; int maxStartHeight = qMax(0, cellHeight - maxRowCount()); if (numSteps > 0 && startHeight() + numSteps >= maxStartHeight) { newHeight = maxStartHeight; } else { newHeight = startHeight() + numSteps; } if (newHeight == startHeight()) { return; } setStartHeight(newHeight); foreach (MonthItem *manager, mManagerList) { manager->updateGeometry(); } invalidate(QRectF(), BackgroundLayer); event->accept(); */ } void MonthScene::scrollCellsDown() { int newHeight = startHeight() + 1; setStartHeight(newHeight); for (MonthItem *manager : qAsConst(mManagerList)) { manager->updateGeometry(); } invalidate(QRectF(), BackgroundLayer); } void MonthScene::scrollCellsUp() { int newHeight = startHeight() - 1; setStartHeight(newHeight); for (MonthItem *manager : qAsConst(mManagerList)) { manager->updateGeometry(); } invalidate(QRectF(), BackgroundLayer); } void MonthScene::clickOnScrollIndicator(ScrollIndicator *scrollItem) { if (scrollItem->direction() == ScrollIndicator::UpArrow) { scrollCellsUp(); } else if (scrollItem->direction() == ScrollIndicator::DownArrow) { scrollCellsDown(); } } void MonthScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *mouseEvent) { QPointF pos = mouseEvent->scenePos(); repeatTimer.stop(); MonthGraphicsItem *iItem = dynamic_cast<MonthGraphicsItem *>(itemAt(pos, {})); if (iItem) { if (iItem->monthItem()) { IncidenceMonthItem *tmp = qobject_cast<IncidenceMonthItem *>(iItem->monthItem()); if (tmp) { selectItem(iItem->monthItem()); mMonthView->defaultAction(tmp->akonadiItem()); mouseEvent->accept(); } } } else { Q_EMIT newEventSignal(); } } void MonthScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) { QPointF pos = mouseEvent->scenePos(); MonthGraphicsView *view = static_cast<MonthGraphicsView *>(views().at(0)); // Change cursor depending on the part of the item it hovers to inform // the user that he can resize the item. if (mActionType == None) { MonthGraphicsItem *iItem = dynamic_cast<MonthGraphicsItem *>(itemAt(pos, {})); if (iItem) { if (iItem->monthItem()->isResizable() && iItem->isBeginItem() && iItem->mapFromScene(pos).x() <= 10) { view->setActionCursor(Resize); } else if (iItem->monthItem()->isResizable() && iItem->isEndItem() && iItem->mapFromScene(pos).x() >= iItem->boundingRect().width() - 10) { view->setActionCursor(Resize); } else { view->setActionCursor(None); } } else { view->setActionCursor(None); } mouseEvent->accept(); return; } // If an item was selected during the click, we maybe have an item to move ! if (mActionItem) { MonthCell *currentCell = getCellFromPos(pos); // Initiate action if not already done if (!mActionInitiated && mActionType != None) { if (mActionType == Move) { mActionItem->beginMove(); } else if (mActionType == Resize) { mActionItem->beginResize(); } mActionInitiated = true; } view->setActionCursor(mActionType); // Move or resize action if (currentCell && currentCell != mPreviousCell) { bool ok = true; if (mActionType == Move) { mActionItem->moveBy(mPreviousCell->date().daysTo(currentCell->date())); } else if (mActionType == Resize) { ok = mActionItem->resizeBy(mPreviousCell->date().daysTo(currentCell->date())); } if (ok) { mPreviousCell = currentCell; } mActionItem->updateGeometry(); update(); } mouseEvent->accept(); } } void MonthScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) { QPointF pos = mouseEvent->scenePos(); mClickedItem = nullptr; mCurrentIndicator = nullptr; MonthGraphicsItem *iItem = dynamic_cast<MonthGraphicsItem *>(itemAt(pos, {})); if (iItem) { mClickedItem = iItem->monthItem(); selectItem(mClickedItem); if (mouseEvent->button() == Qt::RightButton) { IncidenceMonthItem *tmp = qobject_cast<IncidenceMonthItem *>(mClickedItem); if (tmp) { Q_EMIT showIncidencePopupSignal(tmp->akonadiItem(), tmp->realStartDate()); } } if (mouseEvent->button() == Qt::LeftButton) { // Basic initialization for resize and move mActionItem = mClickedItem; mStartCell = getCellFromPos(pos); mPreviousCell = mStartCell; mActionInitiated = false; // Move or resize ? if (iItem->monthItem()->isResizable() && iItem->isBeginItem() && iItem->mapFromScene(pos).x() <= 10) { mActionType = Resize; mResizeType = ResizeLeft; } else if (iItem->monthItem()->isResizable() && iItem->isEndItem() && iItem->mapFromScene(pos).x() >= iItem->boundingRect().width() - 10) { mActionType = Resize; mResizeType = ResizeRight; } else if (iItem->monthItem()->isMoveable()) { mActionType = Move; } } mouseEvent->accept(); } else if (ScrollIndicator *scrollItem = dynamic_cast<ScrollIndicator *>(itemAt(pos, {}))) { clickOnScrollIndicator(scrollItem); mCurrentIndicator = scrollItem; repeatTimer.start(AUTO_REPEAT_DELAY, this); } else { // unselect items when clicking somewhere else selectItem(nullptr); MonthCell *cell = getCellFromPos(pos); if (cell) { mSelectedCellDate = cell->date(); update(); if (mouseEvent->button() == Qt::RightButton) { Q_EMIT showNewEventPopupSignal(); } mouseEvent->accept(); } } } void MonthScene::timerEvent(QTimerEvent *e) { if (e->timerId() == repeatTimer.timerId()) { if (mCurrentIndicator->isVisible()) { clickOnScrollIndicator(mCurrentIndicator); repeatTimer.start(AUTO_REPEAT_DELAY, this); } else { mCurrentIndicator = nullptr; repeatTimer.stop(); } } } void MonthScene::helpEvent(QGraphicsSceneHelpEvent *helpEvent) { // Find the first item that does tooltips const QPointF pos = helpEvent->scenePos(); MonthGraphicsItem *toolTipItem = dynamic_cast<MonthGraphicsItem *>(itemAt(pos, {})); // Show or hide the tooltip QString text; QPoint point; if (toolTipItem) { text = toolTipItem->getToolTip(); point = helpEvent->screenPos(); } QToolTip::showText(point, text, helpEvent->widget()); helpEvent->setAccepted(!text.isEmpty()); } void MonthScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) { QPointF pos = mouseEvent->scenePos(); static_cast<MonthGraphicsView *>(views().at(0))->setActionCursor(None); repeatTimer.stop(); mCurrentIndicator = nullptr; if (mActionItem) { MonthCell *currentCell = getCellFromPos(pos); const bool somethingChanged = currentCell && currentCell != mStartCell; if (somethingChanged) { // We want to act if a move really happened if (mActionType == Resize) { mActionItem->endResize(); } else if (mActionType == Move) { mActionItem->endMove(); } } mActionItem = nullptr; mActionType = None; mStartCell = nullptr; mouseEvent->accept(); } } // returns true if the point is in the monthgrid (allows to avoid selecting a cell when // a click is outside the month grid bool MonthScene::isInMonthGrid(int x, int y) const { return x >= 0 && y >= 0 && x <= availableWidth() && y <= availableHeight(); } // The function converts the coordinates to the month grid coordinates to // be able to locate the cell. MonthCell *MonthScene::getCellFromPos(const QPointF &pos) { int y = sceneYToMonthGridY(static_cast<int>(pos.y())); int x = sceneXToMonthGridX(static_cast<int>(pos.x())); if (!isInMonthGrid(x, y)) { return nullptr; } int id = (int)(y / rowHeight()) * 7 + (int)(x / columnWidth()); return mMonthCellMap.value(mMonthView->actualStartDateTime().date().addDays(id)); } void MonthScene::selectItem(MonthItem *item) { /* if (mSelectedItem == item) { return; } I commented the above code so it's possible to selected a selected item. korg-mobile needs that, otherwise clicking on a selected item wont bring the editor up. Another solution would be to have two Q_SIGNALS: incidenceSelected() and incidenceClicked() */ IncidenceMonthItem *tmp = qobject_cast<IncidenceMonthItem *>(item); if (!tmp) { mSelectedItem = nullptr; Q_EMIT incidenceSelected(Akonadi::Item(), QDate()); return; } mSelectedItem = item; Q_ASSERT(CalendarSupport::hasIncidence(tmp->akonadiItem())); if (mMonthView->selectedIncidenceDates().isEmpty()) { Q_EMIT incidenceSelected(tmp->akonadiItem(), QDate()); } else { Q_EMIT incidenceSelected(tmp->akonadiItem(), mMonthView->selectedIncidenceDates().at(0)); } update(); } void MonthScene::removeIncidence(const QString &uid) { for (MonthItem *manager : qAsConst(mManagerList)) { IncidenceMonthItem *imi = qobject_cast<IncidenceMonthItem *>(manager); if (!imi) { continue; } KCalendarCore::Incidence::Ptr incidence = imi->incidence(); if (!incidence) { continue; } if (incidence->uid() == uid) { const auto lst = imi->monthGraphicsItems(); for (MonthGraphicsItem *gitem : lst) { removeItem(gitem); } } } } //---------------------------------------------------------- MonthGraphicsView::MonthGraphicsView(MonthView *parent) : QGraphicsView(parent) , mMonthView(parent) { setMouseTracking(true); } void MonthGraphicsView::setActionCursor(MonthScene::ActionType actionType) { switch (actionType) { case MonthScene::Move: #ifndef QT_NO_CURSOR setCursor(Qt::ArrowCursor); #endif break; case MonthScene::Resize: #ifndef QT_NO_CURSOR setCursor(Qt::SizeHorCursor); #endif break; #ifndef QT_NO_CURSOR default: setCursor(Qt::ArrowCursor); #endif } } void MonthGraphicsView::setScene(MonthScene *scene) { mScene = scene; QGraphicsView::setScene(scene); } void MonthGraphicsView::resizeEvent(QResizeEvent *event) { mScene->setSceneRect(0, 0, event->size().width(), event->size().height()); mScene->updateGeometry(); } 07070100000036000081A40000000200000002000000015F0BF3C900002310000000000000000000000000000000000000004500000000eventviews-VERSIONgit.20200713T074025~752bb43/src/month/monthscene.h/* Copyright (c) 2008 Bruno Virlet <bruno.virlet@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_MONTHSCENE_H #define EVENTVIEWS_MONTHSCENE_H #include <Collection> #include <Item> #include <QBasicTimer> #include <QDate> #include <QGraphicsScene> #include <QGraphicsView> #include <QMap> namespace Akonadi { class IncidenceChanger; } namespace EventViews { class MonthCell; class MonthItem; class MonthView; class ScrollIndicator; class MonthScene : public QGraphicsScene { Q_OBJECT enum ActionType { None, Move, Resize }; public: enum ResizeType { ResizeLeft, ResizeRight }; explicit MonthScene(MonthView *parent); ~MonthScene() override; Q_REQUIRED_RESULT int columnWidth() const; Q_REQUIRED_RESULT int rowHeight() const; MonthCell *firstCellForMonthItem(MonthItem *manager); Q_REQUIRED_RESULT int height(MonthItem *manager); Q_REQUIRED_RESULT int itemHeight(); Q_REQUIRED_RESULT int itemHeightIncludingSpacing(); QList<MonthItem *> mManagerList; MonthView *mMonthView = nullptr; MonthView *monthView() const { return mMonthView; } QMap<QDate, MonthCell *> mMonthCellMap; Q_REQUIRED_RESULT bool initialized() const { return mInitialized; } void setInitialized(bool i) { mInitialized = i; } void resetAll(); Akonadi::IncidenceChanger *incidenceChanger() const; int totalHeight(); /** * Returns the vertical position where the top of the cell should be * painted taking in account margins, rowHeight */ Q_REQUIRED_RESULT int cellVerticalPos(const MonthCell *cell) const; /** * Idem, for the horizontal position */ Q_REQUIRED_RESULT int cellHorizontalPos(const MonthCell *cell) const; /** Select item. If the argument is 0, the currently selected item gets deselected. This function emits the itemSelected(bool) signal to inform about selection/deselection of events. */ void selectItem(MonthItem *); Q_REQUIRED_RESULT int maxRowCount(); MonthCell *selectedCell() const; MonthCell *previousCell() const; /** Get the space on the right of the cell associated to the date @p date. */ Q_REQUIRED_RESULT int getRightSpan(const QDate &date) const; /** Get the space on the left of the cell associated to the date @p date. */ Q_REQUIRED_RESULT int getLeftSpan(const QDate &date) const; /** Returns the date in the first column of the row given by @p row. */ Q_REQUIRED_RESULT QDate firstDateOnRow(int row) const; /** Calls updateGeometry() on each MonthItem */ void updateGeometry(); /** Returns the first height. Used for scrolling @see MonthItem::height() */ Q_REQUIRED_RESULT int startHeight() const { return mStartHeight; } /** Set the current height using @p height. If height = 0, then the view is not scrolled. Else it will be scrolled by step of one item. */ void setStartHeight(int height) { mStartHeight = height; } /** Returns the resize type. */ Q_REQUIRED_RESULT ResizeType resizeType() const { return mResizeType; } /** Returns the currently selected item. */ MonthItem *selectedItem() { return mSelectedItem; } Q_REQUIRED_RESULT QPixmap birthdayPixmap() const { return mBirthdayPixmap; } Q_REQUIRED_RESULT QPixmap anniversaryPixmap() const { return mAnniversaryPixmap; } Q_REQUIRED_RESULT QPixmap alarmPixmap() const { return mAlarmPixmap; } Q_REQUIRED_RESULT QPixmap recurPixmap() const { return mRecurPixmap; } Q_REQUIRED_RESULT QPixmap readonlyPixmap() const { return mReadonlyPixmap; } Q_REQUIRED_RESULT QPixmap replyPixmap() const { return mReplyPixmap; } Q_REQUIRED_RESULT QPixmap holidayPixmap() const { return mHolidayPixmap; } /** Removes an incidence from the scene */ void removeIncidence(const QString &uid); Q_SIGNALS: void incidenceSelected(const Akonadi::Item &incidence, const QDate &); void showIncidencePopupSignal(const Akonadi::Item &, const QDate &); void newEventSignal(); void showNewEventPopupSignal(); protected: void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *mouseEvent) override; void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) override; void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) override; void wheelEvent(QGraphicsSceneWheelEvent *wheelEvent) override; void timerEvent(QTimerEvent *e) override; void helpEvent(QGraphicsSceneHelpEvent *helpEvent) override; /** Scrolls all incidences in cells up */ virtual void scrollCellsUp(); /** Scrolls all incidences in cells down */ virtual void scrollCellsDown(); /** A click on a scroll indicator has occurred TODO : move this handler to the scrollindicator */ virtual void clickOnScrollIndicator(ScrollIndicator *scrollItem); /** Handles drag and drop events. Called from eventFilter. */ // virtual bool eventFilter_drag( QObject *, QDropEvent * ); /** Returns true if the last item is visible in the given @p cell. */ bool lastItemFit(MonthCell *cell); private: /** * Returns the height of the header of the view */ int headerHeight() const; int availableWidth() const; /** * Height available to draw the cells. Doesn't include header. */ int availableHeight() const; /** * Removes all the margins, frames, etc. to give the * X coordinate in the MonthGrid. */ int sceneXToMonthGridX(int xScene); /** * Removes all the margins, frames, headers etc. to give the * Y coordinate in the MonthGrid. */ int sceneYToMonthGridY(int yScene); /** * Given a pos in the scene coordinates, * returns the cell containing @p pos. */ MonthCell *getCellFromPos(const QPointF &pos); /** Returns true if (x, y) is in the monthgrid, false else. */ bool isInMonthGrid(int x, int y) const; bool mInitialized; // User interaction. MonthItem *mClickedItem = nullptr; // todo ini in ctor MonthItem *mActionItem = nullptr; bool mActionInitiated; MonthItem *mSelectedItem = nullptr; QDate mSelectedCellDate; MonthCell *mStartCell = nullptr; // start cell when dragging MonthCell *mPreviousCell = nullptr; // the cell before that one during dragging ActionType mActionType; ResizeType mResizeType; // The item height at the top of the cell. This is generally 0 unless // the user scroll the view when there are too many items. int mStartHeight; // icons to draw in front of the events QPixmap mEventPixmap; QPixmap mBirthdayPixmap; QPixmap mAnniversaryPixmap; QPixmap mTodoPixmap; QPixmap mTodoDonePixmap; QPixmap mJournalPixmap; QPixmap mAlarmPixmap; QPixmap mRecurPixmap; QPixmap mReadonlyPixmap; QPixmap mReplyPixmap; QPixmap mHolidayPixmap; QBasicTimer repeatTimer; ScrollIndicator *mCurrentIndicator = nullptr; friend class MonthGraphicsView; }; /** * Renders a MonthScene */ class MonthGraphicsView : public QGraphicsView { Q_OBJECT public: explicit MonthGraphicsView(MonthView *parent); /** Draws the cells. */ void drawBackground(QPainter *painter, const QRectF &rect) override; void setScene(MonthScene *scene); /** Change the cursor according to @p actionType. */ void setActionCursor(MonthScene::ActionType actionType); protected: void resizeEvent(QResizeEvent *) override; private: MonthScene *mScene = nullptr; MonthView *mMonthView = nullptr; }; } #endif 07070100000037000081A40000000200000002000000015F0BF3C90000503F000000000000000000000000000000000000004600000000eventviews-VERSIONgit.20200713T074025~752bb43/src/month/monthview.cpp/* Copyright (c) 2008 Bruno Virlet <bruno.virlet@gmail.com> Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Bertjan Broeksema, broeksema@kde.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "monthview.h" #include "monthgraphicsitems.h" #include "monthitem.h" #include "monthscene.h" #include "prefs.h" #include <CalendarSupport/CollectionSelection> #include <Akonadi/Calendar/ETMCalendar> #include <CalendarSupport/KCalPrefs> #include <CalendarSupport/Utils> #include <KCalendarCore/OccurrenceIterator> #include <KCheckableProxyModel> #include <KLocalizedString> #include <QIcon> #include "calendarview_debug.h" #include <QHBoxLayout> #include <QTimer> #include <QToolButton> #include <QWheelEvent> using namespace EventViews; namespace EventViews { class MonthViewPrivate : public Akonadi::ETMCalendar::CalendarObserver { MonthView *q; public: /// Methods explicit MonthViewPrivate(MonthView *qq); void addIncidence(const Akonadi::Item &incidence); void moveStartDate(int weeks, int months); // void setUpModels(); void triggerDelayedReload(EventView::Change reason); public: /// Members QTimer reloadTimer; MonthScene *scene = nullptr; QDate selectedItemDate; Akonadi::Item::Id selectedItemId; MonthGraphicsView *view = nullptr; QToolButton *fullView = nullptr; // List of uids for QDate QMap<QDate, QStringList > mBusyDays; protected: /* reimplemented from KCalendarCore::Calendar::CalendarObserver */ void calendarIncidenceAdded(const KCalendarCore::Incidence::Ptr &incidence) override; void calendarIncidenceChanged(const KCalendarCore::Incidence::Ptr &incidence) override; void calendarIncidenceDeleted(const KCalendarCore::Incidence::Ptr &incidence, const KCalendarCore::Calendar *calendar) override; private: //quiet --overloaded-virtual warning using KCalendarCore::Calendar::CalendarObserver::calendarIncidenceDeleted; }; } MonthViewPrivate::MonthViewPrivate(MonthView *qq) : q(qq) , scene(new MonthScene(qq)) , selectedItemId(-1) , view(new MonthGraphicsView(qq)) , fullView(nullptr) { reloadTimer.setSingleShot(true); view->setScene(scene); } void MonthViewPrivate::addIncidence(const Akonadi::Item &incidence) { Q_UNUSED(incidence); //TODO: add some more intelligence here... q->setChanges(q->changes() | EventView::IncidencesAdded); reloadTimer.start(50); } void MonthViewPrivate::moveStartDate(int weeks, int months) { auto start = q->startDateTime(); auto end = q->endDateTime(); start = start.addDays(weeks * 7); end = end.addDays(weeks * 7); start = start.addMonths(months); end = end.addMonths(months); KCalendarCore::DateList dateList; QDate d = start.date(); const QDate e = end.date(); dateList.reserve(d.daysTo(e) + 1); while (d <= e) { dateList.append(d); d = d.addDays(1); } /** * If we call q->setDateRange( start, end ); directly, * it will change the selected dates in month view, * but the application won't know about it. * The correct way is to Q_EMIT datesSelected() * #250256 * */ Q_EMIT q->datesSelected(dateList); } void MonthViewPrivate::triggerDelayedReload(EventView::Change reason) { q->setChanges(q->changes() | reason); if (!reloadTimer.isActive()) { reloadTimer.start(50); } } void MonthViewPrivate::calendarIncidenceAdded(const KCalendarCore::Incidence::Ptr &) { triggerDelayedReload(MonthView::IncidencesAdded); } void MonthViewPrivate::calendarIncidenceChanged(const KCalendarCore::Incidence::Ptr &) { triggerDelayedReload(MonthView::IncidencesEdited); } void MonthViewPrivate::calendarIncidenceDeleted(const KCalendarCore::Incidence::Ptr &incidence, const KCalendarCore::Calendar *calendar) { Q_UNUSED(calendar); Q_ASSERT(!incidence->uid().isEmpty()); scene->removeIncidence(incidence->uid()); } /// MonthView MonthView::MonthView(NavButtonsVisibility visibility, QWidget *parent) : EventView(parent) , d(new MonthViewPrivate(this)) { QHBoxLayout *topLayout = new QHBoxLayout(this); topLayout->addWidget(d->view); topLayout->setContentsMargins(0, 0, 0, 0); if (visibility == Visible) { QVBoxLayout *rightLayout = new QVBoxLayout(); rightLayout->setSpacing(0); rightLayout->setContentsMargins(0, 0, 0, 0); // push buttons to the bottom rightLayout->addStretch(1); d->fullView = new QToolButton(this); d->fullView->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen"))); d->fullView->setAutoRaise(true); d->fullView->setCheckable(true); d->fullView->setChecked(preferences()->fullViewMonth()); d->fullView->isChecked() ? d->fullView->setToolTip(i18nc("@info:tooltip", "Display calendar in a normal size")) : d->fullView->setToolTip(i18nc("@info:tooltip", "Display calendar in a full window")); d->fullView->setWhatsThis( i18nc("@info:whatsthis", "Click this button and the month view will be enlarged to fill the " "maximum available window space / or shrunk back to its normal size.")); connect(d->fullView, &QAbstractButton::clicked, this, &MonthView::changeFullView); QToolButton *minusMonth = new QToolButton(this); minusMonth->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up-double"))); minusMonth->setAutoRaise(true); minusMonth->setToolTip(i18nc("@info:tooltip", "Go back one month")); minusMonth->setWhatsThis( i18nc("@info:whatsthis", "Click this button and the view will be scrolled back in time by 1 month.")); connect(minusMonth, &QAbstractButton::clicked, this, &MonthView::moveBackMonth); QToolButton *minusWeek = new QToolButton(this); minusWeek->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up"))); minusWeek->setAutoRaise(true); minusWeek->setToolTip(i18nc("@info:tooltip", "Go back one week")); minusWeek->setWhatsThis( i18nc("@info:whatsthis", "Click this button and the view will be scrolled back in time by 1 week.")); connect(minusWeek, &QAbstractButton::clicked, this, &MonthView::moveBackWeek); QToolButton *plusWeek = new QToolButton(this); plusWeek->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); plusWeek->setAutoRaise(true); plusWeek->setToolTip(i18nc("@info:tooltip", "Go forward one week")); plusWeek->setWhatsThis( i18nc("@info:whatsthis", "Click this button and the view will be scrolled forward in time by 1 week.")); connect(plusWeek, &QAbstractButton::clicked, this, &MonthView::moveFwdWeek); QToolButton *plusMonth = new QToolButton(this); plusMonth->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down-double"))); plusMonth->setAutoRaise(true); plusMonth->setToolTip(i18nc("@info:tooltip", "Go forward one month")); plusMonth->setWhatsThis( i18nc("@info:whatsthis", "Click this button and the view will be scrolled forward in time by 1 month.")); connect(plusMonth, &QAbstractButton::clicked, this, &MonthView::moveFwdMonth); rightLayout->addWidget(d->fullView); rightLayout->addWidget(minusMonth); rightLayout->addWidget(minusWeek); rightLayout->addWidget(plusWeek); rightLayout->addWidget(plusMonth); topLayout->addLayout(rightLayout); } else { d->view->setFrameStyle(QFrame::NoFrame); } connect(d->scene, &MonthScene::showIncidencePopupSignal, this, &MonthView::showIncidencePopupSignal); connect(d->scene, &MonthScene::incidenceSelected, this, &EventView::incidenceSelected); connect(d->scene, SIGNAL(newEventSignal()), SIGNAL(newEventSignal())); connect(d->scene, &MonthScene::showNewEventPopupSignal, this, &MonthView::showNewEventPopupSignal); connect(&d->reloadTimer, &QTimer::timeout, this, &MonthView::reloadIncidences); updateConfig(); // d->setUpModels(); d->reloadTimer.start(50); } MonthView::~MonthView() { if (calendar()) { calendar()->unregisterObserver(d); } delete d; } void MonthView::updateConfig() { d->scene->update(); setChanges(changes() | ConfigChanged); d->reloadTimer.start(50); } int MonthView::currentDateCount() const { return actualStartDateTime().date().daysTo(actualEndDateTime().date()); } KCalendarCore::DateList MonthView::selectedIncidenceDates() const { KCalendarCore::DateList list; if (d->scene->selectedItem()) { IncidenceMonthItem *tmp = qobject_cast<IncidenceMonthItem *>(d->scene->selectedItem()); if (tmp) { QDate selectedItemDate = tmp->realStartDate(); if (selectedItemDate.isValid()) { list << selectedItemDate; } } } else if (d->scene->selectedCell()) { list << d->scene->selectedCell()->date(); } return list; } QDateTime MonthView::selectionStart() const { if (d->scene->selectedCell()) { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) return QDateTime(d->scene->selectedCell()->date()); #else return QDateTime(d->scene->selectedCell()->date().startOfDay()); #endif } else { return QDateTime(); } } QDateTime MonthView::selectionEnd() const { // Only one cell can be selected (for now) return selectionStart(); } void MonthView::setDateRange(const QDateTime &start, const QDateTime &end, const QDate &preferredMonth) { EventView::setDateRange(start, end, preferredMonth); setChanges(changes() | DatesChanged); d->reloadTimer.start(50); } bool MonthView::eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const { if (d->scene->selectedCell()) { startDt.setDate(d->scene->selectedCell()->date()); endDt.setDate(d->scene->selectedCell()->date()); allDay = true; return true; } return false; } void MonthView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) { Q_UNUSED(incidenceList); Q_UNUSED(date); } void MonthView::changeIncidenceDisplay(const Akonadi::Item &incidence, int action) { Q_UNUSED(incidence); Q_UNUSED(action); //TODO: add some more intelligence here... // don't call reloadIncidences() directly. It would delete all // MonthItems, but this changeIncidenceDisplay()-method was probably // called by one of the MonthItem objects. So only schedule a reload // as event setChanges(changes() | IncidencesEdited); d->reloadTimer.start(50); } void MonthView::updateView() { d->view->update(); } #ifndef QT_NO_WHEELEVENT void MonthView::wheelEvent(QWheelEvent *event) { // invert direction to get scroll-like behaviour if (event->angleDelta().y() > 0) { d->moveStartDate(-1, 0); } else if (event->angleDelta().y() < 0) { d->moveStartDate(1, 0); } // call accept in every case, we do not want anybody else to react event->accept(); } #endif void MonthView::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_PageUp) { d->moveStartDate(0, -1); event->accept(); } else if (event->key() == Qt::Key_PageDown) { d->moveStartDate(0, 1); event->accept(); } else if (processKeyEvent(event)) { event->accept(); } else { event->ignore(); } } void MonthView::keyReleaseEvent(QKeyEvent *event) { if (processKeyEvent(event)) { event->accept(); } else { event->ignore(); } } void MonthView::changeFullView() { bool fullView = d->fullView->isChecked(); if (fullView) { d->fullView->setIcon(QIcon::fromTheme(QStringLiteral("view-restore"))); d->fullView->setToolTip(i18nc("@info:tooltip", "Display calendar in a normal size")); } else { d->fullView->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen"))); d->fullView->setToolTip(i18nc("@info:tooltip", "Display calendar in a full window")); } preferences()->setFullViewMonth(fullView); preferences()->writeConfig(); Q_EMIT fullViewChanged(fullView); } void MonthView::moveBackMonth() { d->moveStartDate(0, -1); } void MonthView::moveBackWeek() { d->moveStartDate(-1, 0); } void MonthView::moveFwdWeek() { d->moveStartDate(1, 0); } void MonthView::moveFwdMonth() { d->moveStartDate(0, 1); } void MonthView::showDates(const QDate &start, const QDate &end, const QDate &preferedMonth) { Q_UNUSED(start); Q_UNUSED(end); Q_UNUSED(preferedMonth); d->triggerDelayedReload(DatesChanged); } QPair<QDateTime, QDateTime> MonthView::actualDateRange(const QDateTime &start, const QDateTime &, const QDate &preferredMonth) const { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) QDateTime dayOne = preferredMonth.isValid() ? QDateTime(preferredMonth) : start; #else QDateTime dayOne = preferredMonth.isValid() ? QDateTime(preferredMonth.startOfDay()) : start; #endif dayOne.setDate(QDate(dayOne.date().year(), dayOne.date().month(), 1)); const int weekdayCol = (dayOne.date().dayOfWeek() + 7 - preferences()->firstDayOfWeek()) % 7; QDateTime actualStart = dayOne.addDays(-weekdayCol); actualStart.setTime(QTime(0, 0, 0, 0)); QDateTime actualEnd = actualStart.addDays(6 * 7 - 1); actualEnd.setTime(QTime(23, 59, 59, 99)); return qMakePair(actualStart, actualEnd); } Akonadi::Item::List MonthView::selectedIncidences() const { Akonadi::Item::List selected; if (d->scene->selectedItem()) { IncidenceMonthItem *tmp = qobject_cast<IncidenceMonthItem *>(d->scene->selectedItem()); if (tmp) { Akonadi::Item incidenceSelected = tmp->akonadiItem(); if (incidenceSelected.isValid()) { selected.append(incidenceSelected); } } } return selected; } void MonthView::reloadIncidences() { if (changes() == NothingChanged) { return; } // keep selection if it exists Akonadi::Item incidenceSelected; MonthItem *itemToReselect = nullptr; if (IncidenceMonthItem *tmp = qobject_cast<IncidenceMonthItem *>(d->scene->selectedItem())) { d->selectedItemId = tmp->akonadiItem().id(); d->selectedItemDate = tmp->realStartDate(); if (!d->selectedItemDate.isValid()) { return; } } d->scene->resetAll(); d->mBusyDays.clear(); // build monthcells hash int i = 0; for (QDate date = actualStartDateTime().date(); date <= actualEndDateTime().date(); date = date.addDays(1)) { d->scene->mMonthCellMap[ date ] = new MonthCell(i, date, d->scene); i++; } // build global event list const bool colorMonthBusyDays = preferences()->colorMonthBusyDays(); KCalendarCore::OccurrenceIterator occurIter(*calendar(), actualStartDateTime(), actualEndDateTime()); while (occurIter.hasNext()) { occurIter.next(); // Remove the two checks when filtering is done through a proxyModel, when using calendar search if (!preferences()->showTodosMonthView() && occurIter.incidence()->type() == KCalendarCore::Incidence::TypeTodo) { continue; } if (!preferences()->showJournalsMonthView() && occurIter.incidence()->type() == KCalendarCore::Incidence::TypeJournal) { continue; } const bool busyDay = colorMonthBusyDays && makesWholeDayBusy(occurIter.incidence()); if (busyDay) { QStringList &list = d->mBusyDays[occurIter.occurrenceStartDate().date()]; list.append(occurIter.incidence()->uid()); } const Akonadi::Item item = calendar()->item(occurIter.incidence()); if (!item.isValid()) { continue; } Q_ASSERT(item.isValid()); Q_ASSERT(item.hasPayload()); MonthItem *manager = new IncidenceMonthItem(d->scene, calendar(), item, occurIter.incidence(), occurIter.occurrenceStartDate().toLocalTime().date()); d->scene->mManagerList << manager; if (d->selectedItemId == item.id() && manager->realStartDate() == d->selectedItemDate) { // only select it outside the loop because we are still creating items itemToReselect = manager; } } if (itemToReselect) { d->scene->selectItem(itemToReselect); } // add holidays const QList<QDate> workDays = CalendarSupport::workDays(actualStartDateTime().date(), actualEndDateTime().date()); for (QDate date = actualStartDateTime().date(); date <= actualEndDateTime().date(); date = date.addDays(1)) { // Only call CalendarSupport::holiday() if it's not a workDay, saves come cpu cicles. if (!workDays.contains(date)) { QStringList holidays(CalendarSupport::holiday(date)); if (!holidays.isEmpty()) { MonthItem *holidayItem = new HolidayMonthItem( d->scene, date, holidays.join(i18nc("@item:intext delimiter for joining holiday names", ","))); d->scene->mManagerList << holidayItem; } } } // sort it std::sort(d->scene->mManagerList.begin(), d->scene->mManagerList.end(), MonthItem::greaterThan); // build each month's cell event list for (MonthItem *manager : qAsConst(d->scene->mManagerList)) { for (QDate date = manager->startDate(); date <= manager->endDate(); date = date.addDays(1)) { MonthCell *cell = d->scene->mMonthCellMap.value(date); if (cell) { cell->mMonthItemList << manager; } } } for (MonthItem *manager : qAsConst(d->scene->mManagerList)) { manager->updateMonthGraphicsItems(); manager->updatePosition(); } for (MonthItem *manager : qAsConst(d->scene->mManagerList)) { manager->updateGeometry(); } d->scene->setInitialized(true); d->view->update(); d->scene->update(); } void MonthView::calendarReset() { qCDebug(CALENDARVIEW_LOG); d->triggerDelayedReload(ResourcesChanged); } QDate MonthView::averageDate() const { return actualStartDateTime().date().addDays( actualStartDateTime().date().daysTo(actualEndDateTime().date()) / 2); } int MonthView::currentMonth() const { return averageDate().month(); } bool MonthView::usesFullWindow() { return preferences()->fullViewMonth(); } bool MonthView::isBusyDay(const QDate &day) const { return !d->mBusyDays[day].isEmpty(); } void MonthView::setCalendar(const Akonadi::ETMCalendar::Ptr &cal) { Q_ASSERT(cal); if (calendar()) { calendar()->unregisterObserver(d); } EventView::setCalendar(cal); calendar()->registerObserver(d); } 07070100000038000081A40000000200000002000000015F0BF3C90000111F000000000000000000000000000000000000004400000000eventviews-VERSIONgit.20200713T074025~752bb43/src/month/monthview.h/* Copyright (c) 2008 Bruno Virlet <bruno.virlet@gmail.com> Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Bertjan Broeksema, broeksema@kde.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_MONTHVIEW_H #define EVENTVIEWS_MONTHVIEW_H #include "eventview.h" class QModelIndex; namespace EventViews { class MonthViewPrivate; /** New month view. */ class EVENTVIEWS_EXPORT MonthView : public EventView { Q_OBJECT public: enum NavButtonsVisibility { Visible, Hidden }; explicit MonthView(NavButtonsVisibility visibility = Visible, QWidget *parent = nullptr); ~MonthView() override; Q_REQUIRED_RESULT int currentDateCount() const override; Q_REQUIRED_RESULT int currentMonth() const; Q_REQUIRED_RESULT Akonadi::Item::List selectedIncidences() const override; /** Returns dates of the currently selected events */ Q_REQUIRED_RESULT KCalendarCore::DateList selectedIncidenceDates() const override; Q_REQUIRED_RESULT QDateTime selectionStart() const override; Q_REQUIRED_RESULT QDateTime selectionEnd() const override; virtual void setDateRange(const QDateTime &start, const QDateTime &end, const QDate &preferredMonth = QDate()) override; Q_REQUIRED_RESULT bool eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const override; /** * Returns the average date in the view */ Q_REQUIRED_RESULT QDate averageDate() const; Q_REQUIRED_RESULT bool usesFullWindow(); Q_REQUIRED_RESULT bool supportsDateRangeSelection() const { return false; } Q_REQUIRED_RESULT bool isBusyDay(const QDate &day) const; void setCalendar(const Akonadi::ETMCalendar::Ptr &cal) override; Q_SIGNALS: void showIncidencePopupSignal(const Akonadi::Item &item, const QDate &date); void showNewEventPopupSignal(); void fullViewChanged(bool enabled); public Q_SLOTS: void updateConfig() override; void updateView() override; void showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) override; void changeIncidenceDisplay(const Akonadi::Item &, int); void changeFullView(); /// Display in full window mode void moveBackMonth(); /// Shift the view one month back void moveBackWeek(); /// Shift the view one week back void moveFwdWeek(); /// Shift the view one week forward void moveFwdMonth(); /// Shift the view one month forward protected Q_SLOTS: void calendarReset() override; private Q_SLOTS: // void dataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight ); // void rowsInserted( const QModelIndex &parent, int start, int end ); // void rowsAboutToBeRemoved( const QModelIndex &parent, int start, int end ); protected: #ifndef QT_NO_WHEELEVENT void wheelEvent(QWheelEvent *event) override; #endif void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; QPair<QDateTime, QDateTime> actualDateRange( const QDateTime &start, const QDateTime &end, const QDate &preferredMonth = QDate()) const override; // Compute and update the whole view void reloadIncidences(); protected: /** * @deprecated */ void showDates(const QDate &start, const QDate &end, const QDate &preferedMonth = QDate()) override; private: MonthViewPrivate *const d; friend class MonthViewPrivate; friend class MonthScene; }; } #endif 07070100000039000041ED0000000200000002000000025F0BF3C900000000000000000000000000000000000000000000003E00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/multiagenda0707010000003A000081A40000000200000002000000015F0BF3C90000051B000000000000000000000000000000000000005600000000eventviews-VERSIONgit.20200713T074025~752bb43/src/multiagenda/configdialoginterface.h/* Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Sergio Martins <sergio.martins@kdab.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef EVENTVIEWS_CONFIGDIALOGINTERFACE_H #define EVENTVIEWS_CONFIGDIALOGINTERFACE_H class KCheckableProxyModel; namespace EventViews { class ConfigDialogInterface { public: virtual ~ConfigDialogInterface() { } virtual int numberOfColumns() const = 0; virtual bool useCustomColumns() const = 0; virtual QString columnTitle(int column) const = 0; virtual KCheckableProxyModel *takeSelectionModel(int column) = 0; }; } #endif 0707010000003B000081A40000000200000002000000015F0BF3C900006B47000000000000000000000000000000000000005200000000eventviews-VERSIONgit.20200713T074025~752bb43/src/multiagenda/multiagendaview.cpp/* Copyright (c) 2007 Volker Krause <vkrause@kde.org> Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Sergio Martins <sergio.martins@kdab.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "multiagendaview.h" #include "prefs.h" #include "agenda/agenda.h" #include "agenda/agendaview.h" #include "agenda/timelabelszone.h" #include "configdialoginterface.h" #include "calendarview_debug.h" #include <AkonadiCore/EntityTreeModel> #include <AkonadiWidgets/ETMViewStateSaver> #include <CalendarSupport/CollectionSelection> #include <CalendarSupport/Utils> #include <KCheckableProxyModel> #include <KLocalizedString> #include <KRearrangeColumnsProxyModel> #include <KViewStateMaintainer> #include <QHBoxLayout> #include <QLabel> #include <QPainter> #include <QResizeEvent> #include <QScrollArea> #include <QScrollBar> #include <QSortFilterProxyModel> #include <QSplitter> #include <QTimer> using namespace Akonadi; using namespace EventViews; /** Function for debugging purposes: prints an object's sizeHint()/minimumSizeHint()/policy and it's children's too, recursively */ /* static void printObject( QObject *o, int level = 0 ) { QMap<int,QString> map; map.insert( 0, "Fixed" ); map.insert( 1, "Minimum" ); map.insert( 4, "Maximum" ); map.insert( 5, "Preferred" ); map.insert( 7, "Expanding" ); map.insert( 3, "MinimumExpaning" ); map.insert( 13, "Ignored" ); QWidget *w = qobject_cast<QWidget*>( o ); if ( w ) { qCDebug(CALENDARVIEW_LOG) << QString( level*2, '-' ) << o << w->sizeHint() << "/" << map[w->sizePolicy().verticalPolicy()] << "; minimumSize = " << w->minimumSize() << "; minimumSizeHint = " << w->minimumSizeHint(); } else { qCDebug(CALENDARVIEW_LOG) << QString( level*2, '-' ) << o ; } foreach( QObject *child, o->children() ) { printObject( child, level + 1 ); } } */ static QString generateColumnLabel(int c) { return i18n("Agenda %1", c + 1); } class Q_DECL_HIDDEN MultiAgendaView::Private { private: class ElidedLabel : public QFrame { public: ElidedLabel(const QString &text) : mText(text) { } QSize minimumSizeHint() const override; protected: void paintEvent(QPaintEvent *event) override; private: QString mText; }; public: Private(MultiAgendaView *qq) : q(qq) , mUpdateOnShow(true) , mPendingChanges(true) , mCustomColumnSetupUsed(false) , mCustomNumberOfColumns(2) { } ~Private() { qDeleteAll(mSelectionSavers); } void addView(const Akonadi::Collection &collection); void addView(KCheckableProxyModel *selectionProxy, const QString &title); AgendaView *createView(const QString &title); void deleteViews(); void setupViews(); void resizeScrollView(const QSize &size); MultiAgendaView *q; QList<AgendaView *> mAgendaViews; QList<QWidget *> mAgendaWidgets; QWidget *mTopBox = nullptr; QScrollArea *mScrollArea = nullptr; TimeLabelsZone *mTimeLabelsZone = nullptr; QSplitter *mLeftSplitter = nullptr; QSplitter *mRightSplitter = nullptr; QScrollBar *mScrollBar = nullptr; QWidget *mLeftBottomSpacer = nullptr; QWidget *mRightBottomSpacer = nullptr; QDate mStartDate, mEndDate; bool mUpdateOnShow; bool mPendingChanges; bool mCustomColumnSetupUsed; QVector<KCheckableProxyModel *> mCollectionSelectionModels; QStringList mCustomColumnTitles; int mCustomNumberOfColumns; QLabel *mLabel = nullptr; QWidget *mRightDummyWidget = nullptr; QHash<QString, KViewStateMaintainer<ETMViewStateSaver> * > mSelectionSavers; }; MultiAgendaView::MultiAgendaView(QWidget *parent) : EventView(parent) , d(new Private(this)) { QHBoxLayout *topLevelLayout = new QHBoxLayout(this); topLevelLayout->setSpacing(0); topLevelLayout->setContentsMargins(0, 0, 0, 0); QFontMetrics fm(font()); int topLabelHeight = 2 * fm.height() + fm.lineSpacing(); QWidget *topSideBox = new QWidget(this); QVBoxLayout *topSideBoxVBoxLayout = new QVBoxLayout(topSideBox); topSideBoxVBoxLayout->setContentsMargins(0, 0, 0, 0); QWidget *topSideSpacer = new QWidget(topSideBox); topSideBoxVBoxLayout->addWidget(topSideSpacer); topSideSpacer->setFixedHeight(topLabelHeight); d->mLeftSplitter = new QSplitter(Qt::Vertical, topSideBox); topSideBoxVBoxLayout->addWidget(d->mLeftSplitter); d->mLabel = new QLabel(i18n("All Day"), d->mLeftSplitter); d->mLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); d->mLabel->setWordWrap(true); QWidget *sideBox = new QWidget(d->mLeftSplitter); QVBoxLayout *sideBoxVBoxLayout = new QVBoxLayout(sideBox); sideBoxVBoxLayout->setContentsMargins(0, 0, 0, 0); // compensate for the frame the agenda views but not the timelabels have QWidget *timeLabelTopAlignmentSpacer = new QWidget(sideBox); sideBoxVBoxLayout->addWidget(timeLabelTopAlignmentSpacer); d->mTimeLabelsZone = new TimeLabelsZone(sideBox, PrefsPtr(new Prefs())); QWidget *timeLabelBotAlignmentSpacer = new QWidget(sideBox); sideBoxVBoxLayout->addWidget(timeLabelBotAlignmentSpacer); d->mLeftBottomSpacer = new QWidget(topSideBox); topSideBoxVBoxLayout->addWidget(d->mLeftBottomSpacer); topLevelLayout->addWidget(topSideBox); d->mScrollArea = new QScrollArea(this); d->mScrollArea->setWidgetResizable(true); d->mScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // BUG: timelabels aren't aligned with the agenda's grid, 2 or 3 pixels of offset. // asymetric since the timelabels timeLabelTopAlignmentSpacer->setFixedHeight(d->mScrollArea->frameWidth() - 1); // have 25 horizontal lines timeLabelBotAlignmentSpacer->setFixedHeight(d->mScrollArea->frameWidth() - 2); d->mScrollArea->setFrameShape(QFrame::NoFrame); topLevelLayout->addWidget(d->mScrollArea, 100); d->mTopBox = new QWidget(d->mScrollArea->viewport()); QHBoxLayout *mTopBoxHBoxLayout = new QHBoxLayout(d->mTopBox); mTopBoxHBoxLayout->setContentsMargins(0, 0, 0, 0); d->mScrollArea->setWidget(d->mTopBox); topSideBox = new QWidget(this); topSideBoxVBoxLayout = new QVBoxLayout(topSideBox); topSideBoxVBoxLayout->setContentsMargins(0, 0, 0, 0); topSideSpacer = new QWidget(topSideBox); topSideBoxVBoxLayout->addWidget(topSideSpacer); topSideSpacer->setFixedHeight(topLabelHeight); d->mRightSplitter = new QSplitter(Qt::Vertical, topSideBox); topSideBoxVBoxLayout->addWidget(d->mRightSplitter); connect(d->mLeftSplitter, &QSplitter::splitterMoved, this, &MultiAgendaView::resizeSplitters); connect(d->mRightSplitter, &QSplitter::splitterMoved, this, &MultiAgendaView::resizeSplitters); d->mRightDummyWidget = new QWidget(d->mRightSplitter); d->mScrollBar = new QScrollBar(Qt::Vertical, d->mRightSplitter); d->mRightBottomSpacer = new QWidget(topSideBox); topSideBoxVBoxLayout->addWidget(d->mRightBottomSpacer); topLevelLayout->addWidget(topSideBox); } void MultiAgendaView::setCalendar(const Akonadi::ETMCalendar::Ptr &calendar) { EventView::setCalendar(calendar); for (KCheckableProxyModel *proxy : qAsConst(d->mCollectionSelectionModels)) { proxy->setSourceModel(calendar->entityTreeModel()); } disconnect(nullptr, SIGNAL(selectionChanged(Akonadi::Collection::List,Akonadi::Collection::List)), this, SLOT(forceRecreateViews())); connect(collectionSelection(), &CalendarSupport::CollectionSelection::selectionChanged, this, &MultiAgendaView::forceRecreateViews); recreateViews(); } void MultiAgendaView::recreateViews() { if (!d->mPendingChanges) { return; } d->mPendingChanges = false; d->deleteViews(); if (d->mCustomColumnSetupUsed) { Q_ASSERT(d->mCollectionSelectionModels.size() == d->mCustomNumberOfColumns); for (int i = 0; i < d->mCustomNumberOfColumns; ++i) { d->addView(d->mCollectionSelectionModels[i], d->mCustomColumnTitles[i]); } } else { const auto lst = collectionSelection()->selectedCollections(); for (const Akonadi::Collection &i : lst) { if (i.contentMimeTypes().contains(KCalendarCore::Event::eventMimeType())) { d->addView(i); } } } // no resources activated, so stop here to avoid crashing somewhere down the line // TODO: show a nice message instead if (d->mAgendaViews.isEmpty()) { return; } d->setupViews(); QTimer::singleShot(0, this, &MultiAgendaView::slotResizeScrollView); d->mTimeLabelsZone->updateAll(); QScrollArea *timeLabel = d->mTimeLabelsZone->timeLabels().at(0); connect(timeLabel->verticalScrollBar(), &QAbstractSlider::valueChanged, d->mScrollBar, &QAbstractSlider::setValue); connect(d->mScrollBar, &QAbstractSlider::valueChanged, timeLabel->verticalScrollBar(), &QAbstractSlider::setValue); resizeSplitters(); QTimer::singleShot(0, this, &MultiAgendaView::setupScrollBar); d->mTimeLabelsZone->updateTimeLabelsPosition(); } void MultiAgendaView::forceRecreateViews() { d->mPendingChanges = true; recreateViews(); } void MultiAgendaView::Private::deleteViews() { for (AgendaView *const i : qAsConst(mAgendaViews)) { KCheckableProxyModel *proxy = i->takeCustomCollectionSelectionProxyModel(); if (proxy && !mCollectionSelectionModels.contains(proxy)) { delete proxy; } delete i; } mAgendaViews.clear(); mTimeLabelsZone->setAgendaView(nullptr); qDeleteAll(mAgendaWidgets); mAgendaWidgets.clear(); } void MultiAgendaView::Private::setupViews() { for (AgendaView *agenda : qAsConst(mAgendaViews)) { q->connect(agenda, SIGNAL(newEventSignal()), q, SIGNAL(newEventSignal())); q->connect(agenda, SIGNAL(newEventSignal(QDate)), q, SIGNAL(newEventSignal(QDate))); q->connect(agenda, SIGNAL(newEventSignal(QDateTime)), q, SIGNAL(newEventSignal(QDateTime))); q->connect(agenda, SIGNAL(newEventSignal(QDateTime,QDateTime)), q, SIGNAL(newEventSignal(QDateTime,QDateTime))); q->connect(agenda, &EventView::editIncidenceSignal, q, &EventView::editIncidenceSignal); q->connect(agenda, &EventView::showIncidenceSignal, q, &EventView::showIncidenceSignal); q->connect(agenda, &EventView::deleteIncidenceSignal, q, &EventView::deleteIncidenceSignal); q->connect(agenda, &EventView::incidenceSelected, q, &EventView::incidenceSelected); q->connect(agenda, &EventView::cutIncidenceSignal, q, &EventView::cutIncidenceSignal); q->connect(agenda, &EventView::copyIncidenceSignal, q, &EventView::copyIncidenceSignal); q->connect(agenda, &EventView::pasteIncidenceSignal, q, &EventView::pasteIncidenceSignal); q->connect(agenda, &EventView::toggleAlarmSignal, q, &EventView::toggleAlarmSignal); q->connect(agenda, &EventView::dissociateOccurrencesSignal, q, &EventView::dissociateOccurrencesSignal); q->connect(agenda, &EventView::newTodoSignal, q, &EventView::newTodoSignal); q->connect(agenda, &EventView::incidenceSelected, q, &MultiAgendaView::slotSelectionChanged); q->connect(agenda, &AgendaView::timeSpanSelectionChanged, q, &MultiAgendaView::slotClearTimeSpanSelection); q->disconnect(agenda->agenda(), SIGNAL(zoomView(int,QPoint,Qt::Orientation)), agenda, nullptr); q->connect(agenda->agenda(), &Agenda::zoomView, q, &MultiAgendaView::zoomView); } AgendaView *lastView = mAgendaViews.last(); for (AgendaView *agenda : qAsConst(mAgendaViews)) { if (agenda != lastView) { connect(agenda->agenda()->verticalScrollBar(), &QAbstractSlider::valueChanged, lastView->agenda()->verticalScrollBar(), &QAbstractSlider::setValue); } } for (AgendaView *agenda : qAsConst(mAgendaViews)) { agenda->readSettings(); } } MultiAgendaView::~MultiAgendaView() { delete d; } Akonadi::Item::List MultiAgendaView::selectedIncidences() const { Akonadi::Item::List list; for (AgendaView *agendaView : qAsConst(d->mAgendaViews)) { list += agendaView->selectedIncidences(); } return list; } KCalendarCore::DateList MultiAgendaView::selectedIncidenceDates() const { KCalendarCore::DateList list; for (AgendaView *agendaView : qAsConst(d->mAgendaViews)) { list += agendaView->selectedIncidenceDates(); } return list; } int MultiAgendaView::currentDateCount() const { for (AgendaView *agendaView : qAsConst(d->mAgendaViews)) { return agendaView->currentDateCount(); } return 0; } void MultiAgendaView::showDates(const QDate &start, const QDate &end, const QDate &preferredMonth) { Q_UNUSED(preferredMonth); d->mStartDate = start; d->mEndDate = end; slotResizeScrollView(); d->mTimeLabelsZone->updateAll(); for (AgendaView *agendaView : qAsConst(d->mAgendaViews)) { agendaView->showDates(start, end); } } void MultiAgendaView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) { for (AgendaView *agendaView : qAsConst(d->mAgendaViews)) { agendaView->showIncidences(incidenceList, date); } } void MultiAgendaView::updateView() { recreateViews(); for (AgendaView *agendaView : qAsConst(d->mAgendaViews)) { agendaView->updateView(); } } int MultiAgendaView::maxDatesHint() const { // these maxDatesHint functions aren't used return AgendaView::MAX_DAY_COUNT; } void MultiAgendaView::slotSelectionChanged() { for (AgendaView *agenda : qAsConst(d->mAgendaViews)) { if (agenda != sender()) { agenda->clearSelection(); } } } bool MultiAgendaView::eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const { for (AgendaView *agenda : qAsConst(d->mAgendaViews)) { bool valid = agenda->eventDurationHint(startDt, endDt, allDay); if (valid) { return true; } } return false; } void MultiAgendaView::slotClearTimeSpanSelection() { for (AgendaView *agenda : qAsConst(d->mAgendaViews)) { if (agenda != sender()) { agenda->clearTimeSpanSelection(); } else { setCollectionId(agenda->collectionId()); } } } AgendaView *MultiAgendaView::Private::createView(const QString &title) { QWidget *box = new QWidget(mTopBox); mTopBox->layout()->addWidget(box); QVBoxLayout *layout = new QVBoxLayout(box); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(new ElidedLabel(title)); AgendaView *av = new AgendaView(q->preferences(), q->startDateTime().date(), q->endDateTime().date(), true, true, q); layout->addWidget(av); av->setCalendar(q->calendar()); av->setIncidenceChanger(q->changer()); av->agenda()->scrollArea()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); mAgendaViews.append(av); mAgendaWidgets.append(box); box->show(); mTimeLabelsZone->setAgendaView(av); q->connect(mScrollBar, &QAbstractSlider::valueChanged, av->agenda()->verticalScrollBar(), &QAbstractSlider::setValue); q->connect(av->splitter(), &QSplitter::splitterMoved, q, &MultiAgendaView::resizeSplitters); q->connect(av, &AgendaView::showIncidencePopupSignal, q, &MultiAgendaView::showIncidencePopupSignal); q->connect(av, &AgendaView::showNewEventPopupSignal, q, &MultiAgendaView::showNewEventPopupSignal); const QSize minHint = av->allDayAgenda()->scrollArea()->minimumSizeHint(); if (minHint.isValid()) { mLabel->setMinimumHeight(minHint.height()); mRightDummyWidget->setMinimumHeight(minHint.height()); } return av; } void MultiAgendaView::Private::addView(const Akonadi::Collection &collection) { AgendaView *av = createView(CalendarSupport::displayName(q->calendar().data(), collection)); av->setCollectionId(collection.id()); } void MultiAgendaView::Private::addView(KCheckableProxyModel *sm, const QString &title) { AgendaView *av = createView(title); av->setCustomCollectionSelectionProxyModel(sm); } void MultiAgendaView::resizeEvent(QResizeEvent *ev) { d->resizeScrollView(ev->size()); EventView::resizeEvent(ev); } void MultiAgendaView::Private::resizeScrollView(const QSize &size) { const int widgetWidth = size.width() - mTimeLabelsZone->width() - mScrollBar->width(); int height = size.height(); if (mScrollArea->horizontalScrollBar()->isVisible()) { const int sbHeight = mScrollArea->horizontalScrollBar()->height(); height -= sbHeight; mLeftBottomSpacer->setFixedHeight(sbHeight); mRightBottomSpacer->setFixedHeight(sbHeight); } else { mLeftBottomSpacer->setFixedHeight(0); mRightBottomSpacer->setFixedHeight(0); } mTopBox->resize(widgetWidth, height); } void MultiAgendaView::setIncidenceChanger(Akonadi::IncidenceChanger *changer) { EventView::setIncidenceChanger(changer); for (AgendaView *agenda : qAsConst(d->mAgendaViews)) { agenda->setIncidenceChanger(changer); } } void MultiAgendaView::setPreferences(const PrefsPtr &prefs) { for (AgendaView *agenda : qAsConst(d->mAgendaViews)) { agenda->setPreferences(prefs); } EventView::setPreferences(prefs); } void MultiAgendaView::updateConfig() { EventView::updateConfig(); d->mTimeLabelsZone->setPreferences(preferences()); d->mTimeLabelsZone->updateAll(); for (AgendaView *agenda : qAsConst(d->mAgendaViews)) { agenda->updateConfig(); } } void MultiAgendaView::resizeSplitters() { if (d->mAgendaViews.isEmpty()) { return; } QSplitter *lastMovedSplitter = qobject_cast<QSplitter *>(sender()); if (!lastMovedSplitter) { lastMovedSplitter = d->mLeftSplitter; } for (AgendaView *agenda : qAsConst(d->mAgendaViews)) { if (agenda->splitter() == lastMovedSplitter) { continue; } agenda->splitter()->setSizes(lastMovedSplitter->sizes()); } if (lastMovedSplitter != d->mLeftSplitter) { d->mLeftSplitter->setSizes(lastMovedSplitter->sizes()); } if (lastMovedSplitter != d->mRightSplitter) { d->mRightSplitter->setSizes(lastMovedSplitter->sizes()); } } void MultiAgendaView::zoomView(const int delta, const QPoint &pos, const Qt::Orientation ori) { const int hourSz = preferences()->hourSize(); if (ori == Qt::Vertical) { if (delta > 0) { if (hourSz > 4) { preferences()->setHourSize(hourSz - 1); } } else { preferences()->setHourSize(hourSz + 1); } } for (AgendaView *agenda : qAsConst(d->mAgendaViews)) { agenda->zoomView(delta, pos, ori); } d->mTimeLabelsZone->updateAll(); } void MultiAgendaView::slotResizeScrollView() { d->resizeScrollView(size()); } void MultiAgendaView::showEvent(QShowEvent *event) { EventView::showEvent(event); if (d->mUpdateOnShow) { d->mUpdateOnShow = false; d->mPendingChanges = true; // force a full view recreation showDates(d->mStartDate, d->mEndDate); } } void MultiAgendaView::setChanges(Changes changes) { EventView::setChanges(changes); for (AgendaView *agenda : qAsConst(d->mAgendaViews)) { agenda->setChanges(changes); } } void MultiAgendaView::setupScrollBar() { if (!d->mAgendaViews.isEmpty() && d->mAgendaViews.first()->agenda()) { QScrollBar *scrollBar = d->mAgendaViews.first()->agenda()->verticalScrollBar(); d->mScrollBar->setMinimum(scrollBar->minimum()); d->mScrollBar->setMaximum(scrollBar->maximum()); d->mScrollBar->setSingleStep(scrollBar->singleStep()); d->mScrollBar->setPageStep(scrollBar->pageStep()); d->mScrollBar->setValue(scrollBar->value()); } } void MultiAgendaView::collectionSelectionChanged() { qCDebug(CALENDARVIEW_LOG); d->mPendingChanges = true; recreateViews(); } bool MultiAgendaView::hasConfigurationDialog() const { /** The wrapper in korg has the dialog. Too complicated to move to CalendarViews. Depends on korg/AkonadiCollectionView, and will be refactored some day to get rid of CollectionSelectionProxyModel/EntityStateSaver */ return false; } void MultiAgendaView::doRestoreConfig(const KConfigGroup &configGroup) { if (!calendar()) { qCCritical(CALENDARVIEW_LOG) << "Calendar is not set."; Q_ASSERT(false); return; } d->mCustomColumnSetupUsed = configGroup.readEntry("UseCustomColumnSetup", false); d->mCustomNumberOfColumns = configGroup.readEntry("CustomNumberOfColumns", 2); d->mCustomColumnTitles = configGroup.readEntry("ColumnTitles", QStringList()); if (d->mCustomColumnTitles.size() != d->mCustomNumberOfColumns) { const int orig = d->mCustomColumnTitles.size(); d->mCustomColumnTitles.reserve(d->mCustomNumberOfColumns); for (int i = orig; i < d->mCustomNumberOfColumns; ++i) { d->mCustomColumnTitles.push_back(generateColumnLabel(i)); } } QVector<KCheckableProxyModel *> oldModels = d->mCollectionSelectionModels; d->mCollectionSelectionModels.clear(); if (d->mCustomColumnSetupUsed) { d->mCollectionSelectionModels.resize(d->mCustomNumberOfColumns); for (int i = 0; i < d->mCustomNumberOfColumns; ++i) { // Sort the calanders by name QSortFilterProxyModel *sortProxy = new QSortFilterProxyModel(this); sortProxy->setDynamicSortFilter(true); sortProxy->setSourceModel(calendar()->entityTreeModel()); // Only show the first column KRearrangeColumnsProxyModel *columnFilterProxy = new KRearrangeColumnsProxyModel(this); columnFilterProxy->setSourceColumns( QVector<int>() << Akonadi::ETMCalendar::CollectionTitle); columnFilterProxy->setSourceModel(sortProxy); // Keep track of selection. QItemSelectionModel *qsm = new QItemSelectionModel(columnFilterProxy); // Make the model checkable. KCheckableProxyModel *checkableProxy = new KCheckableProxyModel(this); checkableProxy->setSourceModel(columnFilterProxy); checkableProxy->setSelectionModel(qsm); const QString groupName = configGroup.name() + QLatin1String("_subView_") + QString::number(i); const KConfigGroup group = configGroup.config()->group(groupName); if (!d->mSelectionSavers.contains(groupName)) { d->mSelectionSavers.insert(groupName, new KViewStateMaintainer<ETMViewStateSaver>(group)); d->mSelectionSavers[groupName]->setSelectionModel(checkableProxy->selectionModel()); } d->mSelectionSavers[groupName]->restoreState(); d->mCollectionSelectionModels[i] = checkableProxy; } } d->mPendingChanges = true; recreateViews(); qDeleteAll(oldModels); } void MultiAgendaView::doSaveConfig(KConfigGroup &configGroup) { configGroup.writeEntry("UseCustomColumnSetup", d->mCustomColumnSetupUsed); configGroup.writeEntry("CustomNumberOfColumns", d->mCustomNumberOfColumns); configGroup.writeEntry("ColumnTitles", d->mCustomColumnTitles); int idx = 0; for (KCheckableProxyModel *checkableProxyModel : qAsConst(d->mCollectionSelectionModels)) { const QString groupName = configGroup.name() + QLatin1String("_subView_") + QString::number( idx); KConfigGroup group = configGroup.config()->group(groupName); ++idx; //TODO never used ? KViewStateMaintainer<ETMViewStateSaver> saver(group); if (!d->mSelectionSavers.contains(groupName)) { d->mSelectionSavers.insert(groupName, new KViewStateMaintainer<ETMViewStateSaver>(group)); d->mSelectionSavers[groupName]->setSelectionModel(checkableProxyModel->selectionModel()); } d->mSelectionSavers[groupName]->saveState(); } } void MultiAgendaView::customCollectionsChanged(ConfigDialogInterface *dlg) { if (!d->mCustomColumnSetupUsed && !dlg->useCustomColumns()) { // Config didn't change, no need to recreate views return; } d->mCustomColumnSetupUsed = dlg->useCustomColumns(); d->mCustomNumberOfColumns = dlg->numberOfColumns(); QVector<KCheckableProxyModel *> newModels; newModels.resize(d->mCustomNumberOfColumns); d->mCustomColumnTitles.clear(); d->mCustomColumnTitles.reserve(d->mCustomNumberOfColumns); for (int i = 0; i < d->mCustomNumberOfColumns; ++i) { newModels[i] = dlg->takeSelectionModel(i); d->mCustomColumnTitles.push_back(dlg->columnTitle(i)); } d->mCollectionSelectionModels = newModels; d->mPendingChanges = true; recreateViews(); } bool MultiAgendaView::customColumnSetupUsed() const { return d->mCustomColumnSetupUsed; } int MultiAgendaView::customNumberOfColumns() const { return d->mCustomNumberOfColumns; } QVector<KCheckableProxyModel *> MultiAgendaView::collectionSelectionModels() const { return d->mCollectionSelectionModels; } QStringList MultiAgendaView::customColumnTitles() const { return d->mCustomColumnTitles; } void MultiAgendaView::Private::ElidedLabel::paintEvent(QPaintEvent *event) { QFrame::paintEvent(event); QPainter p(this); QRect r = contentsRect(); const QString elidedText = fontMetrics().elidedText(mText, Qt::ElideMiddle, r.width()); p.drawText(r, Qt::AlignHCenter | Qt::AlignVCenter, elidedText); } QSize MultiAgendaView::Private::ElidedLabel::minimumSizeHint() const { const QFontMetrics &fm = fontMetrics(); return QSize(fm.boundingRect(QStringLiteral("...")).width(), fm.height()); } 0707010000003C000081A40000000200000002000000015F0BF3C900000DA8000000000000000000000000000000000000005000000000eventviews-VERSIONgit.20200713T074025~752bb43/src/multiagenda/multiagendaview.h/* Copyright (c) 2007 Volker Krause <vkrause@kde.org> Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Sergio Martins <sergio.martins@kdab.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef EVENTVIEWS_MULTIAGENDAVIEW_H_H #define EVENTVIEWS_MULTIAGENDAVIEW_H_H #include "eventview.h" #include <QDateTime> namespace EventViews { class ConfigDialogInterface; /** Shows one agenda for every resource side-by-side. */ class EVENTVIEWS_EXPORT MultiAgendaView : public EventView { Q_OBJECT public: explicit MultiAgendaView(QWidget *parent = nullptr); ~MultiAgendaView() override; Q_REQUIRED_RESULT Akonadi::Item::List selectedIncidences() const override; Q_REQUIRED_RESULT KCalendarCore::DateList selectedIncidenceDates() const override; Q_REQUIRED_RESULT int currentDateCount() const override; Q_REQUIRED_RESULT int maxDatesHint() const; Q_REQUIRED_RESULT bool eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const override; void setCalendar(const Akonadi::ETMCalendar::Ptr &cal) override; Q_REQUIRED_RESULT bool hasConfigurationDialog() const override; void setChanges(Changes changes) override; Q_REQUIRED_RESULT bool customColumnSetupUsed() const; Q_REQUIRED_RESULT int customNumberOfColumns() const; Q_REQUIRED_RESULT QStringList customColumnTitles() const; Q_REQUIRED_RESULT QVector<KCheckableProxyModel *> collectionSelectionModels() const; void setPreferences(const PrefsPtr &prefs) override; Q_SIGNALS: void showNewEventPopupSignal(); void showIncidencePopupSignal(const Akonadi::Item &, const QDate &); public Q_SLOTS: void customCollectionsChanged(ConfigDialogInterface *dlg); void showDates(const QDate &start, const QDate &end, const QDate &preferredMonth = QDate()) override; void showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) override; void updateView() override; void updateConfig() override; void setIncidenceChanger(Akonadi::IncidenceChanger *changer) override; protected: void resizeEvent(QResizeEvent *event) override; void showEvent(QShowEvent *event) override; void doRestoreConfig(const KConfigGroup &configGroup) override; void doSaveConfig(KConfigGroup &configGroup) override; protected Q_SLOTS: /** * Reimplemented from KOrg::BaseView */ void collectionSelectionChanged(); private Q_SLOTS: void slotSelectionChanged(); void slotClearTimeSpanSelection(); void resizeSplitters(); void setupScrollBar(); void zoomView(const int delta, const QPoint &pos, const Qt::Orientation ori); void slotResizeScrollView(); void recreateViews(); void forceRecreateViews(); private: class Private; Private *const d; }; } #endif 0707010000003D000081A40000000200000002000000015F0BF3C900007D6F000000000000000000000000000000000000003C00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/prefs.cpp/* Copyright (c) 2001,2003 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "prefs.h" #include "prefs_base.h" #include "calendarview_debug.h" #include <AkonadiCore/AttributeFactory> #include <AkonadiCore/CollectionColorAttribute> #include <QFontDatabase> #include <QRandomGenerator> using namespace EventViews; QSet<EventViews::EventView::ItemIcon> iconArrayToSet(const QByteArray &array) { QSet<EventViews::EventView::ItemIcon> set; for (int i = 0; i < array.count(); ++i) { if (i >= EventViews::EventView::IconCount) { qCWarning(CALENDARVIEW_LOG) << "Icon array is too big: " << array.count(); return set; } if (array[i] != 0) { set.insert(static_cast<EventViews::EventView::ItemIcon>(i)); } } return set; } QByteArray iconSetToArray(const QSet<EventViews::EventView::ItemIcon> &set) { QByteArray array; for (int i = 0; i < EventViews::EventView::IconCount; ++i) { const bool contains = set.contains(static_cast<EventViews::EventView::ItemIcon>(i)); array.append(contains ? 1 : 0); } return array; } QByteArray agendaViewIconDefaults() { QByteArray iconDefaults; iconDefaults.resize(7); iconDefaults[EventViews::EventView::CalendarCustomIcon] = 1; iconDefaults[EventViews::EventView::TaskIcon] = 1; iconDefaults[EventViews::EventView::JournalIcon] = 1; iconDefaults[EventViews::EventView::RecurringIcon] = 1; iconDefaults[EventViews::EventView::ReminderIcon] = 1; iconDefaults[EventViews::EventView::ReadOnlyIcon] = 1; iconDefaults[EventViews::EventView::ReplyIcon] = 0; return iconDefaults; } QByteArray monthViewIconDefaults() { QByteArray iconDefaults; iconDefaults.resize(7); iconDefaults[EventViews::EventView::CalendarCustomIcon] = 1; iconDefaults[EventViews::EventView::TaskIcon] = 1; iconDefaults[EventViews::EventView::JournalIcon] = 1; iconDefaults[EventViews::EventView::RecurringIcon] = 0; iconDefaults[EventViews::EventView::ReminderIcon] = 0; iconDefaults[EventViews::EventView::ReadOnlyIcon] = 1; iconDefaults[EventViews::EventView::ReplyIcon] = 0; return iconDefaults; } class BaseConfig : public PrefsBase { public: BaseConfig(); void setResourceColor(const QString &resource, const QColor &color); void setTimeScaleTimezones(const QStringList &timeZones); QStringList timeScaleTimezones() const; public: QHash<QString, QColor> mResourceColors; QColor mDefaultResourceColor; QFont mDefaultMonthViewFont; QFont mDefaultAgendaTimeLabelsFont; QStringList mTimeScaleTimeZones; QSet<EventViews::EventView::ItemIcon> mAgendaViewIcons; QSet<EventViews::EventView::ItemIcon> mMonthViewIcons; protected: void usrSetDefaults() override; void usrRead() override; bool usrSave() override; }; BaseConfig::BaseConfig() : PrefsBase() { mDefaultResourceColor = QColor(); //Default is a color invalid mDefaultAgendaTimeLabelsFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); // make a large default time bar font, at least 16 points. mDefaultAgendaTimeLabelsFont.setPointSize( qMax(mDefaultAgendaTimeLabelsFont.pointSize() + 4, 16)); mDefaultMonthViewFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); // make it a bit smaller mDefaultMonthViewFont.setPointSize( qMax(mDefaultMonthViewFont.pointSize() - 2, 6)); agendaTimeLabelsFontItem()->setDefaultValue(mDefaultAgendaTimeLabelsFont); agendaTimeLabelsFontItem()->setDefault(); monthViewFontItem()->setDefaultValue(mDefaultMonthViewFont); monthViewFontItem()->setDefault(); } void BaseConfig::setResourceColor(const QString &resource, const QColor &color) { mResourceColors.insert(resource, color); } void BaseConfig::setTimeScaleTimezones(const QStringList &list) { mTimeScaleTimeZones = list; } QStringList BaseConfig::timeScaleTimezones() const { return mTimeScaleTimeZones; } void BaseConfig::usrSetDefaults() { setAgendaTimeLabelsFont(mDefaultAgendaTimeLabelsFont); setMonthViewFont(mDefaultMonthViewFont); PrefsBase::usrSetDefaults(); } void BaseConfig::usrRead() { KConfigGroup rColorsConfig(config(), "Resources Colors"); const QStringList colorKeyList = rColorsConfig.keyList(); for (const QString &key : colorKeyList) { QColor color = rColorsConfig.readEntry(key, mDefaultResourceColor); //qCDebug(CALENDARVIEW_LOG) << "key:" << key << "value:" << color; setResourceColor(key, color); } #if 0 config()->setGroup("FreeBusy"); if (mRememberRetrievePw) { mRetrievePassword = KStringHandler::obscure(config()->readEntry("Retrieve Server Password")); } #endif KConfigGroup timeScaleConfig(config(), "Timescale"); setTimeScaleTimezones(timeScaleConfig.readEntry("Timescale Timezones", QStringList())); KConfigGroup monthViewConfig(config(), "Month View"); KConfigGroup agendaViewConfig(config(), "Agenda View"); const QByteArray agendaIconArray = agendaViewConfig.readEntry<QByteArray>("agendaViewItemIcons", agendaViewIconDefaults()); const QByteArray monthIconArray = monthViewConfig.readEntry<QByteArray>("monthViewItemIcons", monthViewIconDefaults()); mAgendaViewIcons = iconArrayToSet(agendaIconArray); mMonthViewIcons = iconArrayToSet(monthIconArray); KConfigSkeleton::usrRead(); } bool BaseConfig::usrSave() { KConfigGroup rColorsConfig(config(), "Resources Colors"); for (auto it = mResourceColors.constBegin(); it != mResourceColors.constEnd(); ++it) { rColorsConfig.writeEntry(it.key(), it.value()); } #if 0 if (mRememberRetrievePw) { config()->writeEntry("Retrieve Server Password", KStringHandler::obscure(mRetrievePassword)); } else { config()->deleteEntry("Retrieve Server Password"); } #endif KConfigGroup timeScaleConfig(config(), "Timescale"); timeScaleConfig.writeEntry("Timescale Timezones", timeScaleTimezones()); KConfigGroup monthViewConfig(config(), "Month View"); KConfigGroup agendaViewConfig(config(), "Agenda View"); const QByteArray agendaIconArray = iconSetToArray(mAgendaViewIcons); const QByteArray monthIconArray = iconSetToArray(mMonthViewIcons); agendaViewConfig.writeEntry<QByteArray>("agendaViewItemIcons", agendaIconArray); monthViewConfig.writeEntry<QByteArray>("monthViewItemIcons", monthIconArray); return KConfigSkeleton::usrSave(); } class Q_DECL_HIDDEN Prefs::Private { public: Private(Prefs *parent) : mAppConfig(nullptr) , q(parent) { } Private(Prefs *parent, KCoreConfigSkeleton *appConfig) : mAppConfig(appConfig) , q(parent) { } KConfigSkeletonItem *appConfigItem(const KConfigSkeletonItem *baseConfigItem) const; void setBool(KCoreConfigSkeleton::ItemBool *baseConfigItem, bool value); bool getBool(const KCoreConfigSkeleton::ItemBool *baseConfigItem) const; void setInt(KCoreConfigSkeleton::ItemInt *baseConfigItem, int value); int getInt(const KCoreConfigSkeleton::ItemInt *baseConfigItem) const; void setString(KCoreConfigSkeleton::ItemString *baseConfigItem, const QString &value); QString getString(const KCoreConfigSkeleton::ItemString *baseConfigItem) const; void setDateTime(KCoreConfigSkeleton::ItemDateTime *baseConfigItem, const QDateTime &value); QDateTime getDateTime(const KCoreConfigSkeleton::ItemDateTime *baseConfigItem) const; void setStringList(KCoreConfigSkeleton::ItemStringList *baseConfigItem, const QStringList &value); QStringList getStringList(const KCoreConfigSkeleton::ItemStringList *baseConfigItem) const; void setColor(KConfigSkeleton::ItemColor *baseConfigItem, const QColor &value); QColor getColor(const KConfigSkeleton::ItemColor *baseConfigItem) const; void setFont(KConfigSkeleton::ItemFont *baseConfigItem, const QFont &value); QFont getFont(const KConfigSkeleton::ItemFont *baseConfigItem) const; public: BaseConfig mBaseConfig; KCoreConfigSkeleton *mAppConfig = nullptr; private: Prefs *q; }; KConfigSkeletonItem *Prefs::Private::appConfigItem( const KConfigSkeletonItem *baseConfigItem) const { Q_ASSERT(baseConfigItem); if (mAppConfig) { return mAppConfig->findItem(baseConfigItem->name()); } return nullptr; } void Prefs::Private::setBool(KCoreConfigSkeleton::ItemBool *baseConfigItem, bool value) { KConfigSkeletonItem *appItem = appConfigItem(baseConfigItem); if (appItem) { KCoreConfigSkeleton::ItemBool *item = dynamic_cast<KCoreConfigSkeleton::ItemBool *>(appItem); if (item) { item->setValue(value); } else { qCCritical(CALENDARVIEW_LOG) << "Application config item" << appItem->name() << "is not of type Bool"; } } else { baseConfigItem->setValue(value); } } bool Prefs::Private::getBool(const KCoreConfigSkeleton::ItemBool *baseConfigItem) const { KConfigSkeletonItem *appItem = appConfigItem(baseConfigItem); if (appItem) { KCoreConfigSkeleton::ItemBool *item = dynamic_cast<KCoreConfigSkeleton::ItemBool *>(appItem); if (item) { return item->value(); } qCCritical(CALENDARVIEW_LOG) << "Application config item" << appItem->name() << "is not of type Bool"; } return baseConfigItem->value(); } void Prefs::Private::setInt(KCoreConfigSkeleton::ItemInt *baseConfigItem, int value) { KConfigSkeletonItem *appItem = appConfigItem(baseConfigItem); if (appItem) { KCoreConfigSkeleton::ItemInt *item = dynamic_cast<KCoreConfigSkeleton::ItemInt *>(appItem); if (item) { item->setValue(value); } else { qCCritical(CALENDARVIEW_LOG) << "Application config item" << appItem->name() << "is not of type Int"; } } else { baseConfigItem->setValue(value); } } int Prefs::Private::getInt(const KCoreConfigSkeleton::ItemInt *baseConfigItem) const { KConfigSkeletonItem *appItem = appConfigItem(baseConfigItem); if (appItem) { KCoreConfigSkeleton::ItemInt *item = dynamic_cast<KCoreConfigSkeleton::ItemInt *>(appItem); if (item) { return item->value(); } qCCritical(CALENDARVIEW_LOG) << "Application config item" << appItem->name() << "is not of type Int"; } return baseConfigItem->value(); } void Prefs::Private::setString(KCoreConfigSkeleton::ItemString *baseConfigItem, const QString &value) { KConfigSkeletonItem *appItem = appConfigItem(baseConfigItem); if (appItem) { KCoreConfigSkeleton::ItemString *item = dynamic_cast<KCoreConfigSkeleton::ItemString *>(appItem); if (item) { item->setValue(value); } else { qCCritical(CALENDARVIEW_LOG) << "Application config item" << appItem->name() << "is not of type String"; } } else { baseConfigItem->setValue(value); } } QString Prefs::Private::getString(const KCoreConfigSkeleton::ItemString *baseConfigItem) const { KConfigSkeletonItem *appItem = appConfigItem(baseConfigItem); if (appItem) { KCoreConfigSkeleton::ItemString *item = dynamic_cast<KCoreConfigSkeleton::ItemString *>(appItem); if (item) { return item->value(); } qCCritical(CALENDARVIEW_LOG) << "Application config item" << appItem->name() << "is not of type String"; } return baseConfigItem->value(); } void Prefs::Private::setDateTime(KCoreConfigSkeleton::ItemDateTime *baseConfigItem, const QDateTime &value) { KConfigSkeletonItem *appItem = appConfigItem(baseConfigItem); if (appItem) { KCoreConfigSkeleton::ItemDateTime *item = dynamic_cast<KCoreConfigSkeleton::ItemDateTime *>(appItem); if (item) { item->setValue(value); } else { qCCritical(CALENDARVIEW_LOG) << "Application config item" << appItem->name() << "is not of type DateTime"; } } else { baseConfigItem->setValue(value); } } QDateTime Prefs::Private::getDateTime( const KCoreConfigSkeleton::ItemDateTime *baseConfigItem) const { KConfigSkeletonItem *appItem = appConfigItem(baseConfigItem); if (appItem) { KCoreConfigSkeleton::ItemDateTime *item = dynamic_cast<KCoreConfigSkeleton::ItemDateTime *>(appItem); if (item) { return item->value(); } qCCritical(CALENDARVIEW_LOG) << "Application config item" << appItem->name() << "is not of type DateTime"; } return baseConfigItem->value(); } void Prefs::Private::setStringList(KCoreConfigSkeleton::ItemStringList *baseConfigItem, const QStringList &value) { KConfigSkeletonItem *appItem = appConfigItem(baseConfigItem); if (appItem) { KCoreConfigSkeleton::ItemStringList *item = dynamic_cast<KCoreConfigSkeleton::ItemStringList *>(appItem); if (item) { item->setValue(value); } else { qCCritical(CALENDARVIEW_LOG) << "Application config item" << appItem->name() << "is not of type StringList"; } } else { baseConfigItem->setValue(value); } } QStringList Prefs::Private::getStringList( const KCoreConfigSkeleton::ItemStringList *baseConfigItem) const { KConfigSkeletonItem *appItem = appConfigItem(baseConfigItem); if (appItem) { KCoreConfigSkeleton::ItemStringList *item = dynamic_cast<KCoreConfigSkeleton::ItemStringList *>(appItem); if (item) { return item->value(); } qCCritical(CALENDARVIEW_LOG) << "Application config item" << appItem->name() << "is not of type StringList"; } return baseConfigItem->value(); } void Prefs::Private::setColor(KConfigSkeleton::ItemColor *baseConfigItem, const QColor &value) { KConfigSkeletonItem *appItem = appConfigItem(baseConfigItem); if (appItem) { KConfigSkeleton::ItemColor *item = dynamic_cast<KConfigSkeleton::ItemColor *>(appItem); if (item) { item->setValue(value); } else { qCCritical(CALENDARVIEW_LOG) << "Application config item" << appItem->name() << "is not of type Color"; } } else { baseConfigItem->setValue(value); } } QColor Prefs::Private::getColor(const KConfigSkeleton::ItemColor *baseConfigItem) const { KConfigSkeletonItem *appItem = appConfigItem(baseConfigItem); if (appItem) { KConfigSkeleton::ItemColor *item = dynamic_cast<KConfigSkeleton::ItemColor *>(appItem); if (item) { return item->value(); } qCCritical(CALENDARVIEW_LOG) << "Application config item" << appItem->name() << "is not of type Color"; } return baseConfigItem->value(); } void Prefs::Private::setFont(KConfigSkeleton::ItemFont *baseConfigItem, const QFont &value) { KConfigSkeletonItem *appItem = appConfigItem(baseConfigItem); if (appItem) { KConfigSkeleton::ItemFont *item = dynamic_cast<KConfigSkeleton::ItemFont *>(appItem); if (item) { item->setValue(value); } else { qCCritical(CALENDARVIEW_LOG) << "Application config item" << appItem->name() << "is not of type Font"; } } else { baseConfigItem->setValue(value); } } QFont Prefs::Private::getFont(const KConfigSkeleton::ItemFont *baseConfigItem) const { KConfigSkeletonItem *appItem = appConfigItem(baseConfigItem); if (appItem) { KConfigSkeleton::ItemFont *item = dynamic_cast<KConfigSkeleton::ItemFont *>(appItem); if (item) { return item->value(); } qCCritical(CALENDARVIEW_LOG) << "Application config item" << appItem->name() << "is not of type Font"; } return baseConfigItem->value(); } Prefs::Prefs() : d(new Private(this)) { // necessary to use CollectionColorAttribute in the EventViews::resourceColor and EventViews::setResourceColor Akonadi::AttributeFactory::registerAttribute<Akonadi::CollectionColorAttribute>(); } Prefs::Prefs(KCoreConfigSkeleton *appConfig) : d(new Private(this, appConfig)) { // necessary to use CollectionColorAttribute in the EventViews::resourceColor and EventViews::setResourceColor Akonadi::AttributeFactory::registerAttribute<Akonadi::CollectionColorAttribute>(); } Prefs::~Prefs() { delete d; } void Prefs::readConfig() { d->mBaseConfig.load(); if (d->mAppConfig) { d->mAppConfig->load(); } } void Prefs::writeConfig() { d->mBaseConfig.save(); if (d->mAppConfig) { d->mAppConfig->save(); } } void Prefs::setUseSystemColor(bool useSystemColor) { d->setBool(d->mBaseConfig.useSystemColorItem(), useSystemColor); } bool Prefs::useSystemColor() const { return d->getBool(d->mBaseConfig.useSystemColorItem()); } void Prefs::setMarcusBainsShowSeconds(bool showSeconds) { d->setBool(d->mBaseConfig.marcusBainsShowSecondsItem(), showSeconds); } bool Prefs::marcusBainsShowSeconds() const { return d->getBool(d->mBaseConfig.marcusBainsShowSecondsItem()); } void Prefs::setAgendaMarcusBainsLineLineColor(const QColor &color) { d->setColor(d->mBaseConfig.agendaMarcusBainsLineLineColorItem(), color); } QColor Prefs::agendaMarcusBainsLineLineColor() const { return d->getColor(d->mBaseConfig.agendaMarcusBainsLineLineColorItem()); } void Prefs::setMarcusBainsEnabled(bool enabled) { d->setBool(d->mBaseConfig.marcusBainsEnabledItem(), enabled); } bool Prefs::marcusBainsEnabled() const { return d->getBool(d->mBaseConfig.marcusBainsEnabledItem()); } void Prefs::setAgendaMarcusBainsLineFont(const QFont &font) { d->setFont(d->mBaseConfig.agendaMarcusBainsLineFontItem(), font); } QFont Prefs::agendaMarcusBainsLineFont() const { return d->getFont(d->mBaseConfig.agendaMarcusBainsLineFontItem()); } void Prefs::setHourSize(int size) { d->setInt(d->mBaseConfig.hourSizeItem(), size); } int Prefs::hourSize() const { return d->getInt(d->mBaseConfig.hourSizeItem()); } void Prefs::setDayBegins(const QDateTime &dateTime) { d->setDateTime(d->mBaseConfig.dayBeginsItem(), dateTime); } QDateTime Prefs::dayBegins() const { return d->getDateTime(d->mBaseConfig.dayBeginsItem()); } void Prefs::setFirstDayOfWeek(const int day) { d->setInt(d->mBaseConfig.weekStartDayItem(), day - 1); } int Prefs::firstDayOfWeek() const { return d->getInt(d->mBaseConfig.weekStartDayItem()) + 1; } void Prefs::setWorkingHoursStart(const QDateTime &dateTime) { d->setDateTime(d->mBaseConfig.workingHoursStartItem(), dateTime); } QDateTime Prefs::workingHoursStart() const { return d->getDateTime(d->mBaseConfig.workingHoursStartItem()); } void Prefs::setWorkingHoursEnd(const QDateTime &dateTime) { d->setDateTime(d->mBaseConfig.workingHoursEndItem(), dateTime); } QDateTime Prefs::workingHoursEnd() const { return d->getDateTime(d->mBaseConfig.workingHoursEndItem()); } void Prefs::setSelectionStartsEditor(bool startEditor) { d->setBool(d->mBaseConfig.selectionStartsEditorItem(), startEditor); } bool Prefs::selectionStartsEditor() const { return d->getBool(d->mBaseConfig.selectionStartsEditorItem()); } void Prefs::setAgendaGridWorkHoursBackgroundColor(const QColor &color) { d->setColor(d->mBaseConfig.agendaGridWorkHoursBackgroundColorItem(), color); } QColor Prefs::agendaGridWorkHoursBackgroundColor() const { return d->getColor(d->mBaseConfig.agendaGridWorkHoursBackgroundColorItem()); } void Prefs::setAgendaGridHighlightColor(const QColor &color) { d->setColor(d->mBaseConfig.agendaGridHighlightColorItem(), color); } QColor Prefs::agendaGridHighlightColor() const { return d->getColor(d->mBaseConfig.agendaGridHighlightColorItem()); } void Prefs::setAgendaGridBackgroundColor(const QColor &color) { d->setColor(d->mBaseConfig.agendaGridBackgroundColorItem(), color); } QColor Prefs::agendaGridBackgroundColor() const { return d->getColor(d->mBaseConfig.agendaGridBackgroundColorItem()); } void Prefs::setEnableAgendaItemIcons(bool enable) { d->setBool(d->mBaseConfig.enableAgendaItemIconsItem(), enable); } bool Prefs::enableAgendaItemIcons() const { return d->getBool(d->mBaseConfig.enableAgendaItemIconsItem()); } void Prefs::setTodosUseCategoryColors(bool useColors) { d->setBool(d->mBaseConfig.todosUseCategoryColorsItem(), useColors); } bool Prefs::todosUseCategoryColors() const { return d->getBool(d->mBaseConfig.todosUseCategoryColorsItem()); } void Prefs::setAgendaHolidaysBackgroundColor(const QColor &color) const { d->setColor(d->mBaseConfig.agendaHolidaysBackgroundColorItem(), color); } QColor Prefs::agendaHolidaysBackgroundColor() const { return d->getColor(d->mBaseConfig.agendaHolidaysBackgroundColorItem()); } void Prefs::setAgendaViewColors(int colors) { d->setInt(d->mBaseConfig.agendaViewColorsItem(), colors); } int Prefs::agendaViewColors() const { return d->getInt(d->mBaseConfig.agendaViewColorsItem()); } void Prefs::setAgendaViewFont(const QFont &font) { d->setFont(d->mBaseConfig.agendaViewFontItem(), font); } QFont Prefs::agendaViewFont() const { return d->getFont(d->mBaseConfig.agendaViewFontItem()); } void Prefs::setMonthViewFont(const QFont &font) { d->setFont(d->mBaseConfig.monthViewFontItem(), font); } QFont Prefs::monthViewFont() const { return d->getFont(d->mBaseConfig.monthViewFontItem()); } QColor Prefs::monthGridBackgroundColor() const { return d->getColor(d->mBaseConfig.monthGridBackgroundColorItem()); } void Prefs::setMonthGridBackgroundColor(const QColor &color) { d->setColor(d->mBaseConfig.monthGridBackgroundColorItem(), color); } QColor Prefs::monthGridWorkHoursBackgroundColor() const { return d->getColor(d->mBaseConfig.monthGridWorkHoursBackgroundColorItem()); } void Prefs::monthGridWorkHoursBackgroundColor(const QColor &color) { d->setColor(d->mBaseConfig.monthGridWorkHoursBackgroundColorItem(), color); } int Prefs::monthViewColors() const { return d->getInt(d->mBaseConfig.monthViewColorsItem()); } void Prefs::setMonthViewColors(int colors) const { d->setInt(d->mBaseConfig.monthViewColorsItem(), colors); } void Prefs::setEnableMonthItemIcons(bool enable) { d->setBool(d->mBaseConfig.enableMonthItemIconsItem(), enable); } bool Prefs::enableMonthItemIcons() const { return d->getBool(d->mBaseConfig.enableMonthItemIconsItem()); } bool Prefs::showTimeInMonthView() const { return d->getBool(d->mBaseConfig.showTimeInMonthViewItem()); } void Prefs::setShowTimeInMonthView(bool show) { d->setBool(d->mBaseConfig.showTimeInMonthViewItem(), show); } bool Prefs::showTodosMonthView() const { return d->getBool(d->mBaseConfig.showTodosMonthViewItem()); } void Prefs::setShowTodosMonthView(bool enable) { d->setBool(d->mBaseConfig.showTodosMonthViewItem(), enable); } bool Prefs::showJournalsMonthView() const { return d->getBool(d->mBaseConfig.showJournalsMonthViewItem()); } void Prefs::setShowJournalsMonthView(bool enable) { d->setBool(d->mBaseConfig.showJournalsMonthViewItem(), enable); } bool Prefs::fullViewMonth() const { return d->getBool(d->mBaseConfig.fullViewMonthItem()); } void Prefs::setFullViewMonth(bool fullView) { d->setBool(d->mBaseConfig.fullViewMonthItem(), fullView); } bool Prefs::sortCompletedTodosSeparately() const { return d->getBool(d->mBaseConfig.sortCompletedTodosSeparatelyItem()); } void Prefs::setSortCompletedTodosSeparately(bool enable) { d->setBool(d->mBaseConfig.sortCompletedTodosSeparatelyItem(), enable); } void Prefs::setEnableToolTips(bool enable) { d->setBool(d->mBaseConfig.enableToolTipsItem(), enable); } bool Prefs::enableToolTips() const { return d->getBool(d->mBaseConfig.enableToolTipsItem()); } void Prefs::setShowTodosAgendaView(bool show) { d->setBool(d->mBaseConfig.showTodosAgendaViewItem(), show); } bool Prefs::showTodosAgendaView() const { return d->getBool(d->mBaseConfig.showTodosAgendaViewItem()); } void Prefs::setAgendaTimeLabelsFont(const QFont &font) { d->setFont(d->mBaseConfig.agendaTimeLabelsFontItem(), font); } QFont Prefs::agendaTimeLabelsFont() const { return d->getFont(d->mBaseConfig.agendaTimeLabelsFontItem()); } QTimeZone Prefs::timeZone() const { return QTimeZone::systemTimeZone(); } bool Prefs::colorAgendaBusyDays() const { return d->getBool(d->mBaseConfig.colorBusyDaysEnabledItem()); } bool Prefs::colorMonthBusyDays() const { return d->getBool(d->mBaseConfig.colorMonthBusyDaysEnabledItem()); } QColor Prefs::viewBgBusyColor() const { return d->getColor(d->mBaseConfig.viewBgBusyColorItem()); } void Prefs::setViewBgBusyColor(const QColor &color) { d->mBaseConfig.mViewBgBusyColor = color; } QColor Prefs::holidayColor() const { return d->getColor(d->mBaseConfig.holidayColorItem()); } void Prefs::setHolidayColor(const QColor &color) { d->mBaseConfig.mHolidayColor = color; } QColor Prefs::agendaViewBackgroundColor() const { return d->getColor(d->mBaseConfig.agendaBgColorItem()); } void Prefs::setAgendaViewBackgroundColor(const QColor &color) { d->mBaseConfig.mAgendaBgColor = color; } QColor Prefs::workingHoursColor() const { return d->getColor(d->mBaseConfig.workingHoursColorItem()); } void Prefs::setWorkingHoursColor(const QColor &color) { d->mBaseConfig.mWorkingHoursColor = color; } QColor Prefs::todoDueTodayColor() const { return d->getColor(d->mBaseConfig.todoDueTodayColorItem()); } void Prefs::setTodoDueTodayColor(const QColor &color) { d->mBaseConfig.mTodoDueTodayColor = color; } QColor Prefs::todoOverdueColor() const { return d->getColor(d->mBaseConfig.todoOverdueColorItem()); } void Prefs::setTodoOverdueColor(const QColor &color) { d->mBaseConfig.mTodoOverdueColor = color; } void Prefs::setColorAgendaBusyDays(bool enable) { d->mBaseConfig.mColorBusyDaysEnabled = enable; } void Prefs::setColorMonthBusyDays(bool enable) { d->mBaseConfig.mColorMonthBusyDaysEnabled = enable; } void Prefs::setResourceColor(const QString &cal, const QColor &color) { d->mBaseConfig.setResourceColor(cal, color); } QColor Prefs::resourceColorKnown(const QString &cal) const { QColor color; if (!cal.isEmpty()) { color = d->mBaseConfig.mResourceColors.value(cal); } return color; } QColor Prefs::resourceColor(const QString &cal) { if (cal.isEmpty()) { return d->mBaseConfig.mDefaultResourceColor; } QColor color = resourceColorKnown(cal); // assign default color if enabled if (!color.isValid() && d->getBool(d->mBaseConfig.assignDefaultResourceColorsItem())) { color.setRgb(0x37, 0x7A, 0xBC); // blueish const int seed = d->getInt(d->mBaseConfig.defaultResourceColorSeedItem()); const QStringList colors = d->getStringList(d->mBaseConfig.defaultResourceColorsItem()); if (seed > 0 && seed - 1 < colors.size()) { color.setNamedColor(colors[seed - 1]); } else { color.setRgb(QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256)); } d->setInt(d->mBaseConfig.defaultResourceColorSeedItem(), (seed + 1)); d->mBaseConfig.setResourceColor(cal, color); } if (color.isValid()) { return color; } else { return d->mBaseConfig.mDefaultResourceColor; } } QStringList Prefs::timeScaleTimezones() const { return d->mBaseConfig.timeScaleTimezones(); } void Prefs::setTimeScaleTimezones(const QStringList &list) { d->mBaseConfig.setTimeScaleTimezones(list); } KConfigSkeleton::ItemFont *Prefs::fontItem(const QString &name) const { KConfigSkeletonItem *item = d->mAppConfig ? d->mAppConfig->findItem(name) : nullptr; if (!item) { item = d->mBaseConfig.findItem(name); } return dynamic_cast<KConfigSkeleton::ItemFont *>(item); } QStringList Prefs::selectedPlugins() const { return d->mBaseConfig.mSelectedPlugins; } QStringList Prefs::decorationsAtAgendaViewTop() const { return d->mBaseConfig.decorationsAtAgendaViewTop(); } QStringList Prefs::decorationsAtAgendaViewBottom() const { return d->mBaseConfig.decorationsAtAgendaViewBottom(); } void Prefs::setSelectedPlugins(const QStringList &plugins) { d->mBaseConfig.setSelectedPlugins(plugins); } void Prefs::setDecorationsAtAgendaViewTop(const QStringList &decorations) { d->mBaseConfig.setDecorationsAtAgendaViewTop(decorations); } void Prefs::setDecorationsAtAgendaViewBottom(const QStringList &decorations) { d->mBaseConfig.setDecorationsAtAgendaViewBottom(decorations); } QSet<EventViews::EventView::ItemIcon> Prefs::agendaViewIcons() const { return d->mBaseConfig.mAgendaViewIcons; } void Prefs::setAgendaViewIcons(const QSet<EventViews::EventView::ItemIcon> &icons) { d->mBaseConfig.mAgendaViewIcons = icons; } QSet<EventViews::EventView::ItemIcon> Prefs::monthViewIcons() const { return d->mBaseConfig.mMonthViewIcons; } void Prefs::setMonthViewIcons(const QSet<EventViews::EventView::ItemIcon> &icons) { d->mBaseConfig.mMonthViewIcons = icons; } void Prefs::setFlatListTodo(bool enable) { d->mBaseConfig.mFlatListTodo = enable; } bool Prefs::flatListTodo() const { return d->mBaseConfig.mFlatListTodo; } void Prefs::setFullViewTodo(bool enable) { d->mBaseConfig.mFullViewTodo = enable; } bool Prefs::fullViewTodo() const { return d->mBaseConfig.mFullViewTodo; } bool Prefs::enableTodoQuickSearch() const { return d->mBaseConfig.mEnableTodoQuickSearch; } void Prefs::setEnableTodoQuickSearch(bool enable) { d->mBaseConfig.mEnableTodoQuickSearch = enable; } bool Prefs::enableQuickTodo() const { return d->mBaseConfig.mEnableQuickTodo; } void Prefs::setEnableQuickTodo(bool enable) { d->mBaseConfig.mEnableQuickTodo = enable; } bool Prefs::highlightTodos() const { return d->mBaseConfig.mHighlightTodos; } void Prefs::setHighlightTodos(bool highlight) { d->mBaseConfig.mHighlightTodos = highlight; } KConfig *Prefs::config() const { return d->mAppConfig ? d->mAppConfig->config() : d->mBaseConfig.config(); } 0707010000003E000081A40000000200000002000000015F0BF3C900001F35000000000000000000000000000000000000003A00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/prefs.h/* Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_PREFS_H #define EVENTVIEWS_PREFS_H #include "eventviews_export.h" #include "eventview.h" #include <KConfigSkeleton> #include <QTimeZone> namespace EventViews { class EVENTVIEWS_EXPORT Prefs { public: /** Creates an instance of Prefs with just base config */ Prefs(); /** Creates an instance of Prefs with base config and application override config The passed @p appConfig will be queried for matching items whenever one of the accessors is called. If one is found it is used for setting/getting the value otherwise the one from the eventviews base config is used. */ explicit Prefs(KCoreConfigSkeleton *appConfig); ~Prefs(); void readConfig(); void writeConfig(); public: void setMarcusBainsShowSeconds(bool showSeconds); Q_REQUIRED_RESULT bool marcusBainsShowSeconds() const; void setAgendaMarcusBainsLineLineColor(const QColor &color); Q_REQUIRED_RESULT QColor agendaMarcusBainsLineLineColor() const; void setMarcusBainsEnabled(bool enabled); Q_REQUIRED_RESULT bool marcusBainsEnabled() const; void setAgendaMarcusBainsLineFont(const QFont &font); Q_REQUIRED_RESULT QFont agendaMarcusBainsLineFont() const; void setHourSize(int size); Q_REQUIRED_RESULT int hourSize() const; void setDayBegins(const QDateTime &dateTime); Q_REQUIRED_RESULT QDateTime dayBegins() const; void setWorkingHoursStart(const QDateTime &dateTime); Q_REQUIRED_RESULT QDateTime workingHoursStart() const; void setWorkingHoursEnd(const QDateTime &dateTime); Q_REQUIRED_RESULT QDateTime workingHoursEnd() const; void setSelectionStartsEditor(bool startEditor); Q_REQUIRED_RESULT bool selectionStartsEditor() const; void setUseSystemColor(bool useSystemColor); Q_REQUIRED_RESULT bool useSystemColor() const; void setAgendaGridWorkHoursBackgroundColor(const QColor &color); Q_REQUIRED_RESULT QColor agendaGridWorkHoursBackgroundColor() const; void setAgendaGridHighlightColor(const QColor &color); Q_REQUIRED_RESULT QColor agendaGridHighlightColor() const; void setAgendaGridBackgroundColor(const QColor &color); Q_REQUIRED_RESULT QColor agendaGridBackgroundColor() const; void setEnableAgendaItemIcons(bool enable); Q_REQUIRED_RESULT bool enableAgendaItemIcons() const; void setTodosUseCategoryColors(bool useColors); Q_REQUIRED_RESULT bool todosUseCategoryColors() const; void setAgendaHolidaysBackgroundColor(const QColor &color) const; Q_REQUIRED_RESULT QColor agendaHolidaysBackgroundColor() const; void setAgendaViewColors(int colors); Q_REQUIRED_RESULT int agendaViewColors() const; void setAgendaViewFont(const QFont &font); Q_REQUIRED_RESULT QFont agendaViewFont() const; void setMonthViewFont(const QFont &font); Q_REQUIRED_RESULT QFont monthViewFont() const; Q_REQUIRED_RESULT QColor monthGridBackgroundColor() const; void setMonthGridBackgroundColor(const QColor &color); Q_REQUIRED_RESULT QColor monthGridWorkHoursBackgroundColor() const; void monthGridWorkHoursBackgroundColor(const QColor &color); void setMonthViewColors(int colors) const; Q_REQUIRED_RESULT int monthViewColors() const; Q_REQUIRED_RESULT bool enableMonthItemIcons() const; void setEnableMonthItemIcons(bool enable); Q_REQUIRED_RESULT bool showTimeInMonthView() const; void setShowTimeInMonthView(bool show); Q_REQUIRED_RESULT bool showTodosMonthView() const; void setShowTodosMonthView(bool show); Q_REQUIRED_RESULT bool showJournalsMonthView() const; void setShowJournalsMonthView(bool show); Q_REQUIRED_RESULT bool fullViewMonth() const; void setFullViewMonth(bool fullView); Q_REQUIRED_RESULT bool sortCompletedTodosSeparately() const; void setSortCompletedTodosSeparately(bool sort); void setEnableToolTips(bool enable); Q_REQUIRED_RESULT bool enableToolTips() const; void setShowTodosAgendaView(bool show); Q_REQUIRED_RESULT bool showTodosAgendaView() const; void setAgendaTimeLabelsFont(const QFont &font); Q_REQUIRED_RESULT QFont agendaTimeLabelsFont() const; KConfigSkeleton::ItemFont *fontItem(const QString &name) const; void setResourceColor(const QString &, const QColor &); Q_REQUIRED_RESULT QColor resourceColor(const QString &); Q_REQUIRED_RESULT QColor resourceColorKnown(const QString &) const; Q_REQUIRED_RESULT QTimeZone timeZone() const; Q_REQUIRED_RESULT QStringList timeScaleTimezones() const; void setTimeScaleTimezones(const QStringList &list); Q_REQUIRED_RESULT QStringList selectedPlugins() const; void setSelectedPlugins(const QStringList &); Q_REQUIRED_RESULT QStringList decorationsAtAgendaViewTop() const; void setDecorationsAtAgendaViewTop(const QStringList &); Q_REQUIRED_RESULT QStringList decorationsAtAgendaViewBottom() const; void setDecorationsAtAgendaViewBottom(const QStringList &); Q_REQUIRED_RESULT bool colorAgendaBusyDays() const; void setColorAgendaBusyDays(bool enable); Q_REQUIRED_RESULT bool colorMonthBusyDays() const; void setColorMonthBusyDays(bool enable); Q_REQUIRED_RESULT QColor viewBgBusyColor() const; void setViewBgBusyColor(const QColor &); Q_REQUIRED_RESULT QColor holidayColor() const; void setHolidayColor(const QColor &color); Q_REQUIRED_RESULT QColor agendaViewBackgroundColor() const; void setAgendaViewBackgroundColor(const QColor &color); Q_REQUIRED_RESULT QColor workingHoursColor() const; void setWorkingHoursColor(const QColor &color); Q_REQUIRED_RESULT QColor todoDueTodayColor() const; void setTodoDueTodayColor(const QColor &color); Q_REQUIRED_RESULT QColor todoOverdueColor() const; void setTodoOverdueColor(const QColor &color); Q_REQUIRED_RESULT QSet<EventViews::EventView::ItemIcon> agendaViewIcons() const; void setAgendaViewIcons(const QSet<EventViews::EventView::ItemIcon> &icons); Q_REQUIRED_RESULT QSet<EventViews::EventView::ItemIcon> monthViewIcons() const; void setMonthViewIcons(const QSet<EventViews::EventView::ItemIcon> &icons); void setFlatListTodo(bool); Q_REQUIRED_RESULT bool flatListTodo() const; void setFullViewTodo(bool); Q_REQUIRED_RESULT bool fullViewTodo() const; Q_REQUIRED_RESULT bool enableTodoQuickSearch() const; void setEnableTodoQuickSearch(bool enable); Q_REQUIRED_RESULT bool enableQuickTodo() const; void setEnableQuickTodo(bool enable); Q_REQUIRED_RESULT bool highlightTodos() const; void setHighlightTodos(bool); void setFirstDayOfWeek(const int day); Q_REQUIRED_RESULT int firstDayOfWeek() const; KConfig *config() const; private: class Private; Private *const d; }; typedef QSharedPointer<Prefs> PrefsPtr; } #endif 0707010000003F000081A40000000200000002000000015F0BF3C900000155000000000000000000000000000000000000004300000000eventviews-VERSIONgit.20200713T074025~752bb43/src/prefs_base.kcfgc# Code generation options for kconfig_compiler File=eventviews.kcfg ClassName=PrefsBase Singleton=false Mutators=true MemberVariables=public GlobalEnums=true ItemAccessors=true SetUserTexts=true NameSpace=EventViews CategoryLoggingName=CALENDARVIEW_LOG IncludeFiles=calendarview_debug.h TranslationDomain=libeventviews TranslationSystem=kde 07070100000040000041ED0000000200000002000000025F0BF3C900000000000000000000000000000000000000000000003B00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/timeline07070100000041000081A40000000200000002000000015F0BF3C900001351000000000000000000000000000000000000004C00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/timeline/timelineitem.cpp/* Copyright (c) 2007 Volker Krause <vkrause@kde.org> Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Andras Mantia <andras@kdab.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timelineitem.h" #include <KGantt/KGanttGlobal> #include <CalendarSupport/Utils> #include <KCalendarCore/Incidence> #include <KCalUtils/IncidenceFormatter> using namespace KCalendarCore; using namespace KCalUtils; using namespace EventViews; TimelineItem::TimelineItem(const Akonadi::ETMCalendar::Ptr &calendar, uint index, QStandardItemModel *model, QObject *parent) : QObject(parent) , mCalendar(calendar) , mModel(model) , mIndex(index) { mModel->removeRow(mIndex); QStandardItem *dummyItem = new QStandardItem; dummyItem->setData(KGantt::TypeTask, KGantt::ItemTypeRole); mModel->insertRow(mIndex, dummyItem); } void TimelineItem::insertIncidence(const Akonadi::Item &aitem, const QDateTime &_start, const QDateTime &_end) { const Incidence::Ptr incidence = CalendarSupport::incidence(aitem); QDateTime start(_start); QDateTime end(_end); if (!start.isValid()) { start = incidence->dtStart().toLocalTime(); } if (!end.isValid()) { end = incidence->dateTime(Incidence::RoleEnd).toLocalTime(); } if (incidence->allDay()) { end = end.addDays(1); } typedef QList<QStandardItem *> ItemList; ItemList list = mItemMap.value(aitem.id()); for (ItemList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { if (static_cast<TimelineSubItem *>(*it)->startTime() == start && static_cast<TimelineSubItem *>(*it)->endTime() == end) { return; } } TimelineSubItem *item = new TimelineSubItem(mCalendar, aitem, this); item->setStartTime(start); item->setOriginalStart(start); item->setEndTime(end); item->setData(mColor, Qt::DecorationRole); list = mModel->takeRow(mIndex); mItemMap[aitem.id()].append(item); list.append(mItemMap[aitem.id()]); mModel->insertRow(mIndex, list); } void TimelineItem::removeIncidence(const Akonadi::Item &incidence) { qDeleteAll(mItemMap.value(incidence.id())); mItemMap.remove(incidence.id()); } void TimelineItem::moveItems(const Akonadi::Item &incidence, int delta, int duration) { typedef QList<QStandardItem *> ItemList; ItemList list = mItemMap.value(incidence.id()); const ItemList::ConstIterator end(list.constEnd()); for (ItemList::ConstIterator it = list.constBegin(); it != end; ++it) { QDateTime start = static_cast<TimelineSubItem *>(*it)->originalStart(); start = start.addSecs(delta); static_cast<TimelineSubItem *>(*it)->setStartTime(start); static_cast<TimelineSubItem *>(*it)->setOriginalStart(start); static_cast<TimelineSubItem *>(*it)->setEndTime(start.addSecs(duration)); } } void TimelineItem::setColor(const QColor &color) { mColor = color; } TimelineSubItem::TimelineSubItem(const Akonadi::ETMCalendar::Ptr &calendar, const Akonadi::Item &incidence, TimelineItem *parent) : QStandardItem() , mCalendar(calendar) , mIncidence(incidence) , mParent(parent) , mToolTipNeedsUpdate(true) { setData(KGantt::TypeTask, KGantt::ItemTypeRole); if (!CalendarSupport::incidence(incidence)->isReadOnly()) { setFlags(Qt::ItemIsSelectable); } } TimelineSubItem::~TimelineSubItem() { } void TimelineSubItem::setStartTime(const QDateTime &dt) { setData(dt, KGantt::StartTimeRole); } QDateTime TimelineSubItem::startTime() const { return data(KGantt::StartTimeRole).toDateTime(); } void TimelineSubItem::setEndTime(const QDateTime &dt) { setData(dt, KGantt::EndTimeRole); } QDateTime TimelineSubItem::endTime() const { return data(KGantt::EndTimeRole).toDateTime(); } void TimelineSubItem::updateToolTip() { if (!mToolTipNeedsUpdate) { return; } mToolTipNeedsUpdate = false; setData(IncidenceFormatter::toolTipStr( CalendarSupport::displayName(mCalendar.data(), mIncidence.parentCollection()), CalendarSupport::incidence(mIncidence), originalStart().date(), true), Qt::ToolTipRole); } 07070100000042000081A40000000200000002000000015F0BF3C900000B19000000000000000000000000000000000000004A00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/timeline/timelineitem.h/* Copyright (c) 2007 Volker Krause <vkrause@kde.org> Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Andras Mantia <andras@kdab.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef EVENTVIEWS_TIMELINEITEM_H #define EVENTVIEWS_TIMELINEITEM_H #include <KCalendarCore/Incidence> #include <Akonadi/Calendar/ETMCalendar> #include <AkonadiCore/Item> #include <QDateTime> #include <QMap> #include <QList> #include <QStandardItemModel> namespace EventViews { class TimelineSubItem; class TimelineItem : public QObject { Q_OBJECT public: TimelineItem(const Akonadi::ETMCalendar::Ptr &calendar, uint index, QStandardItemModel *model, QObject *parent); void insertIncidence(const Akonadi::Item &incidence, const QDateTime &start = QDateTime(), const QDateTime &end = QDateTime()); void removeIncidence(const Akonadi::Item &incidence); void moveItems(const Akonadi::Item &incidence, int delta, int duration); void setColor(const QColor &color); private: Akonadi::ETMCalendar::Ptr mCalendar; QMap<Akonadi::Item::Id, QList<QStandardItem *> > mItemMap; QStandardItemModel *mModel = nullptr; QColor mColor; uint mIndex; }; class TimelineSubItem : public QStandardItem { public: TimelineSubItem(const Akonadi::ETMCalendar::Ptr &calendar, const Akonadi::Item &incidence, TimelineItem *parent); ~TimelineSubItem(); Q_REQUIRED_RESULT Akonadi::Item incidence() const { return mIncidence; } Q_REQUIRED_RESULT QDateTime originalStart() const { return mStart; } void setOriginalStart(const QDateTime &dt) { mStart = dt; } void setStartTime(const QDateTime &dt); Q_REQUIRED_RESULT QDateTime startTime() const; void setEndTime(const QDateTime &dt); Q_REQUIRED_RESULT QDateTime endTime() const; Q_REQUIRED_RESULT TimelineItem *parent() const { return mParent; } void updateToolTip(); private: Akonadi::ETMCalendar::Ptr mCalendar; Akonadi::Item mIncidence; QDateTime mStart; TimelineItem *mParent = nullptr; bool mToolTipNeedsUpdate; }; } #endif 07070100000043000081A40000000200000002000000015F0BF3C900003976000000000000000000000000000000000000004C00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/timeline/timelineview.cpp/* Copyright (c) 2007 Till Adam <adam@kde.org> Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Andras Mantia <andras@kdab.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "timelineview.h" #include "timelineview_p.h" #include "timelineitem.h" #include "helper.h" #include <KGantt/KGanttGraphicsItem> #include <KGantt/KGanttGraphicsView> #include <KGantt/KGanttAbstractRowController> #include <KGantt/KGanttDateTimeGrid> #include <KGantt/KGanttItemDelegate> #include <KGantt/KGanttStyleOptionGanttItem> #include <Akonadi/Calendar/ETMCalendar> #include <CalendarSupport/CollectionSelection> #include <CalendarSupport/Utils> #include <Akonadi/Calendar/IncidenceChanger> #include "calendarview_debug.h" #include <QApplication> #include <QPainter> #include <QStandardItemModel> #include <QSplitter> #include <QTreeWidget> #include <QHeaderView> #include <QPointer> #include <QVBoxLayout> #include <QHelpEvent> #include <KLocalizedString> using namespace KCalendarCore; using namespace EventViews; namespace EventViews { class RowController : public KGantt::AbstractRowController { private: static const int ROW_HEIGHT; QPointer<QAbstractItemModel> m_model; public: RowController() { mRowHeight = 20; } void setModel(QAbstractItemModel *model) { m_model = model; } int headerHeight() const override { return 2 * mRowHeight + 10; } bool isRowVisible(const QModelIndex &) const override { return true; } bool isRowExpanded(const QModelIndex &) const override { return false; } KGantt::Span rowGeometry(const QModelIndex &idx) const override { return KGantt::Span(idx.row() * mRowHeight, mRowHeight); } int maximumItemHeight() const override { return mRowHeight / 2; } int totalHeight() const override { return m_model->rowCount() * mRowHeight; } QModelIndex indexAt(int height) const override { return m_model->index(height / mRowHeight, 0); } QModelIndex indexBelow(const QModelIndex &idx) const override { if (!idx.isValid()) { return QModelIndex(); } return idx.model()->index(idx.row() + 1, idx.column(), idx.parent()); } QModelIndex indexAbove(const QModelIndex &idx) const override { if (!idx.isValid()) { return QModelIndex(); } return idx.model()->index(idx.row() - 1, idx.column(), idx.parent()); } void setRowHeight(int height) { mRowHeight = height; } private: int mRowHeight; }; class GanttHeaderView : public QHeaderView { public: explicit GanttHeaderView(QWidget *parent = nullptr) : QHeaderView(Qt::Horizontal, parent) { } QSize sizeHint() const override { QSize s = QHeaderView::sizeHint(); s.rheight() *= 2; return s; } }; class GanttItemDelegate : public KGantt::ItemDelegate { public: explicit GanttItemDelegate(QObject *parent) : KGantt::ItemDelegate(parent) { } private: void paintGanttItem(QPainter *painter, const KGantt::StyleOptionGanttItem &opt, const QModelIndex &idx) override { painter->setRenderHints(QPainter::Antialiasing); if (!idx.isValid()) { return; } const KGantt::ItemType type = static_cast<KGantt::ItemType>( idx.model()->data(idx, KGantt::ItemTypeRole).toInt()); const QString txt = idx.model()->data(idx, Qt::DisplayRole).toString(); QRectF itemRect = opt.itemRect; QRectF boundingRect = opt.boundingRect; boundingRect.setY(itemRect.y()); boundingRect.setHeight(itemRect.height()); QBrush brush = defaultBrush(type); if (opt.state & QStyle::State_Selected) { QLinearGradient selectedGrad(0., 0., 0., QApplication::fontMetrics().height()); selectedGrad.setColorAt(0., Qt::red); selectedGrad.setColorAt(1., Qt::darkRed); brush = QBrush(selectedGrad); painter->setBrush(brush); } else { painter->setBrush(idx.model()->data(idx, Qt::DecorationRole).value<QColor>()); } painter->setPen(defaultPen(type)); painter->setBrushOrigin(itemRect.topLeft()); switch (type) { case KGantt::TypeTask: if (itemRect.isValid()) { QRectF r = itemRect; painter->drawRect(r); bool drawText = true; Qt::Alignment ta; switch (opt.displayPosition) { case KGantt::StyleOptionGanttItem::Left: ta = Qt::AlignLeft; break; case KGantt::StyleOptionGanttItem::Right: ta = Qt::AlignRight; break; case KGantt::StyleOptionGanttItem::Center: ta = Qt::AlignCenter; break; case KGantt::StyleOptionGanttItem::Hidden: drawText = false; break; } if (drawText) { painter->drawText(boundingRect, ta, txt); } } break; default: KGantt::ItemDelegate::paintGanttItem(painter, opt, idx); break; } } }; } TimelineView::TimelineView(QWidget *parent) : EventView(parent) , d(new Private(this)) { QVBoxLayout *vbox = new QVBoxLayout(this); QSplitter *splitter = new QSplitter(Qt::Horizontal, this); d->mLeftView = new QTreeWidget; d->mLeftView->setHeader(new GanttHeaderView); d->mLeftView->setHeaderLabel(i18n("Calendar")); d->mLeftView->setRootIsDecorated(false); d->mLeftView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); d->mLeftView->setUniformRowHeights(true); d->mGantt = new KGantt::GraphicsView(this); splitter->addWidget(d->mLeftView); splitter->addWidget(d->mGantt); connect(splitter, &QSplitter::splitterMoved, d, &Private::splitterMoved); QStandardItemModel *model = new QStandardItemModel(this); d->mRowController = new RowController; QStyleOptionViewItem opt; opt.initFrom(d->mLeftView); const auto h = d->mLeftView->style()->sizeFromContents(QStyle::CT_ItemViewItem, &opt, QSize(), d->mLeftView).height(); d->mRowController->setRowHeight(h); d->mRowController->setModel(model); d->mGantt->setRowController(d->mRowController); KGantt::DateTimeGrid *grid = new KGantt::DateTimeGrid(); grid->setScale(KGantt::DateTimeGrid::ScaleHour); grid->setDayWidth(800); grid->setRowSeparators(true); d->mGantt->setGrid(grid); d->mGantt->setModel(model); d->mGantt->viewport()->setFixedWidth(8000); d->mGantt->viewport()->installEventFilter(this); d->mGantt->setItemDelegate(new GanttItemDelegate(this)); vbox->addWidget(splitter); connect(model, &QStandardItemModel::itemChanged, d, &Private::itemChanged); connect(d->mGantt, &KGantt::GraphicsView::activated, d, &Private::itemSelected); d->mGantt->setContextMenuPolicy(Qt::CustomContextMenu); connect(d->mGantt, &QWidget::customContextMenuRequested, d, &Private::contextMenuRequested); } TimelineView::~TimelineView() { delete d->mRowController; delete d; } Akonadi::Item::List TimelineView::selectedIncidences() const { return d->mSelectedItemList; } KCalendarCore::DateList TimelineView::selectedIncidenceDates() const { return KCalendarCore::DateList(); } int TimelineView::currentDateCount() const { return 0; } void TimelineView::showDates(const QDate &start, const QDate &end, const QDate &preferredMonth) { Q_UNUSED(preferredMonth); Q_ASSERT_X(calendar(), "showDates()", "set a Akonadi::ETMCalendar"); Q_ASSERT_X(start.isValid(), "showDates()", "start date must be valid"); Q_ASSERT_X(end.isValid(), "showDates()", "end date must be valid"); qCDebug(CALENDARVIEW_LOG) << "start=" << start << "end=" << end; d->mStartDate = start; d->mEndDate = end; d->mHintDate = QDateTime(); KGantt::DateTimeGrid *grid = static_cast<KGantt::DateTimeGrid *>(d->mGantt->grid()); #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) grid->setStartDateTime(QDateTime(start)); #else grid->setStartDateTime(QDateTime(start.startOfDay())); #endif d->mLeftView->clear(); uint index = 0; // item for every calendar TimelineItem *item = nullptr; Akonadi::ETMCalendar::Ptr calres = calendar(); if (!calres) { item = new TimelineItem(calendar(), index++, static_cast<QStandardItemModel *>(d->mGantt->model()), d->mGantt); d->mLeftView->addTopLevelItem(new QTreeWidgetItem(QStringList() << i18n("Calendar"))); d->mCalendarItemMap.insert(-1, item); } else { const CalendarSupport::CollectionSelection *colSel = collectionSelection(); const Akonadi::Collection::List collections = colSel->selectedCollections(); for (const Akonadi::Collection &collection : collections) { if (collection.contentMimeTypes().contains(Event::eventMimeType())) { item = new TimelineItem(calendar(), index++, static_cast<QStandardItemModel *>(d->mGantt->model()), d->mGantt); d->mLeftView->addTopLevelItem( new QTreeWidgetItem( QStringList() << CalendarSupport::displayName( calendar().data(), collection))); const QColor resourceColor = EventViews::resourceColor(collection, preferences()); if (resourceColor.isValid()) { item->setColor(resourceColor); } qCDebug(CALENDARVIEW_LOG) << "Created item " << item << " (" << CalendarSupport::displayName( calendar().data(), collection) << ") " << "with index " << index - 1 << " from collection " << collection.id(); d->mCalendarItemMap.insert(collection.id(), item); } } } // add incidences /** * We remove the model from the view here while we fill it with items, * because every call to insertIncidence will cause the view to do an expensive * updateScene() call otherwise. */ QAbstractItemModel *ganttModel = d->mGantt->model(); d->mGantt->setModel(nullptr); KCalendarCore::Event::List events; for (QDate day = start; day <= end; day = day.addDays(1)) { events = calendar()->events(day, QTimeZone::systemTimeZone(), KCalendarCore::EventSortStartDate, KCalendarCore::SortDirectionAscending); for (const KCalendarCore::Event::Ptr &event : qAsConst(events)) { if (event->hasRecurrenceId()) { continue; } Akonadi::Item item = calendar()->item(event); d->insertIncidence(item, day); } } d->mGantt->setModel(ganttModel); d->splitterMoved(); } void TimelineView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) { Q_UNUSED(incidenceList); Q_UNUSED(date); } void TimelineView::updateView() { if (d->mStartDate.isValid() && d->mEndDate.isValid()) { showDates(d->mStartDate, d->mEndDate); } } void TimelineView::changeIncidenceDisplay(const Akonadi::Item &incidence, int mode) { switch (mode) { case Akonadi::IncidenceChanger::ChangeTypeCreate: d->insertIncidence(incidence); break; case Akonadi::IncidenceChanger::ChangeTypeModify: d->removeIncidence(incidence); d->insertIncidence(incidence); break; case Akonadi::IncidenceChanger::ChangeTypeDelete: d->removeIncidence(incidence); break; default: updateView(); } } bool TimelineView::eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const { startDt = QDateTime(d->mHintDate); endDt = QDateTime(d->mHintDate.addSecs(2 * 60 * 60)); allDay = false; return d->mHintDate.isValid(); } QDate TimelineView::startDate() const { return d->mStartDate; } QDate TimelineView::endDate() const { return d->mEndDate; } bool TimelineView::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::ToolTip) { QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event); QGraphicsItem *item = d->mGantt->itemAt(helpEvent->pos()); if (item) { if (item->type() == KGantt::GraphicsItem::Type) { KGantt::GraphicsItem *graphicsItem = static_cast<KGantt::GraphicsItem *>(item); const QModelIndex itemIndex = graphicsItem->index(); QStandardItemModel *itemModel = qobject_cast<QStandardItemModel *>(d->mGantt->model()); TimelineSubItem *timelineItem = dynamic_cast<TimelineSubItem *>(itemModel->item(itemIndex.row(), itemIndex.column())); if (timelineItem) { timelineItem->updateToolTip(); } } } } return EventView::eventFilter(object, event); } 07070100000044000081A40000000200000002000000015F0BF3C900000A53000000000000000000000000000000000000004A00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/timeline/timelineview.h/* Copyright (c) 2007 Till Adam <adam@kde.org> Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Andras Mantia <andras@kdab.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_TIMELINEVIEW_H #define EVENTVIEWS_TIMELINEVIEW_H #include "eventview.h" #include <Item> #include <QDateTime> namespace EventViews { /** This class provides a view .... */ class EVENTVIEWS_EXPORT TimelineView : public EventView { Q_OBJECT public: explicit TimelineView(QWidget *parent = nullptr); ~TimelineView() override; Q_REQUIRED_RESULT Akonadi::Item::List selectedIncidences() const override; Q_REQUIRED_RESULT KCalendarCore::DateList selectedIncidenceDates() const override; Q_REQUIRED_RESULT int currentDateCount() const override; // ensure start and end are valid before calling this. void showDates(const QDate &, const QDate &, const QDate &preferredMonth = QDate()) override; // FIXME: we already have startDateTime() in the base class // why aren't we using it. Q_REQUIRED_RESULT QDate startDate() const; Q_REQUIRED_RESULT QDate endDate() const; void showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) override; void updateView() override; virtual void changeIncidenceDisplay(const Akonadi::Item &incidence, int mode); Q_REQUIRED_RESULT bool eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const override; Q_SIGNALS: void showNewEventPopupSignal(); void showIncidencePopupSignal(const Akonadi::Item &, const QDate &); protected: bool eventFilter(QObject *object, QEvent *event) override; private: class Private; Private *const d; }; } // namespace EventViews #endif 07070100000045000081A40000000200000002000000015F0BF3C900002102000000000000000000000000000000000000004E00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/timeline/timelineview_p.cpp/* Copyright (c) 2007 Till Adam <adam@kde.org> Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Andras Mantia <andras@kdab.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "timelineview_p.h" #include "timelineitem.h" #include <KGantt/KGanttGraphicsView> #include <Akonadi/Calendar/ETMCalendar> #include <CalendarSupport/CollectionSelection> #include <CalendarSupport/Utils> #include <Akonadi/Calendar/IncidenceChanger> #include <KCalendarCore/OccurrenceIterator> #include "calendarview_debug.h" #include <QStandardItemModel> #include <QTreeWidget> using namespace KCalendarCore; using namespace EventViews; TimelineView::Private::Private(TimelineView *parent) : q(parent) { } TimelineView::Private::~Private() { } void TimelineView::Private::splitterMoved() { mLeftView->setColumnWidth(0, mLeftView->width()); } void TimelineView::Private::itemSelected(const QModelIndex &index) { TimelineSubItem *tlitem = dynamic_cast<TimelineSubItem *>(static_cast<QStandardItemModel *>( mGantt->model())->item(index.row(), index.column())); if (tlitem) { Q_EMIT q->incidenceSelected(tlitem->incidence(), tlitem->originalStart().date()); } } void TimelineView::Private::itemDoubleClicked(const QModelIndex &index) { TimelineSubItem *tlitem = dynamic_cast<TimelineSubItem *>(static_cast<QStandardItemModel *>( mGantt->model())->item(index.row(), index.column())); if (tlitem) { Q_EMIT q->editIncidenceSignal(tlitem->incidence()); } } void TimelineView::Private::contextMenuRequested(const QPoint &point) { QPersistentModelIndex index = mGantt->indexAt(point); // mHintDate = QDateTime( mGantt->getDateTimeForCoordX( QCursor::pos().x(), true ) ); TimelineSubItem *tlitem = dynamic_cast<TimelineSubItem *>(static_cast<QStandardItemModel *>( mGantt->model())->item(index.row(), index.column())); if (!tlitem) { Q_EMIT q->showNewEventPopupSignal(); mSelectedItemList = Akonadi::Item::List(); } else { Q_EMIT q->showIncidencePopupSignal( tlitem->incidence(), CalendarSupport::incidence(tlitem->incidence())->dtStart().date()); mSelectedItemList << tlitem->incidence(); } } //slot void TimelineView::Private::newEventWithHint(const QDateTime &dt) { mHintDate = dt; Q_EMIT q->newEventSignal(dt); } TimelineItem *TimelineView::Private::calendarItemForIncidence(const Akonadi::Item &incidence) { Akonadi::ETMCalendar::Ptr calres = q->calendar(); TimelineItem *item = nullptr; if (!calres) { item = mCalendarItemMap.value(-1); } else { item = mCalendarItemMap.value(incidence.parentCollection().id()); } return item; } void TimelineView::Private::insertIncidence(const Akonadi::Item &aitem, const QDate &day) { const Incidence::Ptr incidence = CalendarSupport::incidence(aitem); //qCDebug(CALENDARVIEW_LOG) << "Item " << aitem.id() << " parentcollection: " << aitem.parentCollection().id(); TimelineItem *item = calendarItemForIncidence(aitem); if (!item) { qCWarning(CALENDARVIEW_LOG) << "Help! Something is really wrong here!"; return; } if (incidence->recurs()) { KCalendarCore::OccurrenceIterator occurIter(*(q->calendar()), incidence, QDateTime(day, QTime(0, 0, 0)), QDateTime( day, QTime(23, 59, 59))); while (occurIter.hasNext()) { occurIter.next(); const Akonadi::Item akonadiItem = q->calendar()->item(occurIter.incidence()); const QDateTime startOfOccurrence = occurIter.occurrenceStartDate(); const QDateTime endOfOccurrence = occurIter.incidence()->endDateForStart( startOfOccurrence); item->insertIncidence(akonadiItem, startOfOccurrence.toLocalTime(), endOfOccurrence.toLocalTime()); } } else { if (incidence->dtStart().date() == day || incidence->dtStart().date() < mStartDate) { item->insertIncidence(aitem); } } } void TimelineView::Private::insertIncidence(const Akonadi::Item &incidence) { const Event::Ptr event = CalendarSupport::event(incidence); if (!event) { return; } if (event->recurs()) { insertIncidence(incidence, QDate()); } for (QDate day = mStartDate; day <= mEndDate; day = day.addDays(1)) { const KCalendarCore::Event::List events = q->calendar()->events(day, QTimeZone::systemTimeZone(), KCalendarCore::EventSortStartDate, KCalendarCore::SortDirectionAscending); if (events.contains(event)) { //PENDING(AKONADI_PORT) check if correct. also check the original if, //was inside the for loop (unnecessarily) for (const KCalendarCore::Event::Ptr &i : events) { Akonadi::Item item = q->calendar()->item(i); insertIncidence(item, day); } } } } void TimelineView::Private::removeIncidence(const Akonadi::Item &incidence) { TimelineItem *item = calendarItemForIncidence(incidence); if (item) { item->removeIncidence(incidence); } else { #if 0 //AKONADI_PORT_DISABLED // try harder, the incidence might already be removed from the resource typedef QMap<QString, KOrg::TimelineItem *> M2_t; typedef QMap<KCalendarCore::ResourceCalendar *, M2_t> M1_t; for (M1_t::ConstIterator it1 = d->mCalendarItemMap.constBegin(); it1 != mCalendarItemMap.constEnd(); ++it1) { for (M2_t::ConstIterator it2 = it1.value().constBegin(); it2 != it1.value().constEnd(); ++it2) { if (it2.value()) { it2.value()->removeIncidence(incidence); } } } #endif } } void TimelineView::Private::itemChanged(QStandardItem *item) { TimelineSubItem *tlit = dynamic_cast<TimelineSubItem *>(item); if (!tlit) { return; } const Akonadi::Item i = tlit->incidence(); const Incidence::Ptr inc = CalendarSupport::incidence(i); QDateTime newStart(tlit->startTime()); if (inc->allDay()) { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) newStart = QDateTime(newStart.date()); #else newStart = QDateTime(newStart.date().startOfDay()); #endif } int delta = tlit->originalStart().secsTo(newStart); inc->setDtStart(inc->dtStart().addSecs(delta)); int duration = tlit->startTime().secsTo(tlit->endTime()); int allDayOffset = 0; if (inc->allDay()) { int secsPerDay = 60 * 60 * 24; duration /= secsPerDay; duration *= secsPerDay; allDayOffset = secsPerDay; duration -= allDayOffset; if (duration < 0) { duration = 0; } } inc->setDuration(duration); TimelineItem *parent = tlit->parent(); parent->moveItems(i, tlit->originalStart().secsTo(newStart), duration + allDayOffset); } 07070100000046000081A40000000200000002000000015F0BF3C9000009F5000000000000000000000000000000000000004C00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/timeline/timelineview_p.h/* Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Sérgio Martins <sergio.martins@kdab.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_TIMELINEVIEW_P_H #define EVENTVIEWS_TIMELINEVIEW_P_H #include "timelineview.h" #include <Collection> #include <AkonadiCore/Item> #include <QMap> #include <QModelIndex> #include <QObject> class QStandardItem; class QTreeWidget; namespace KGantt { class GraphicsView; } namespace EventViews { class TimelineItem; class RowController; class TimelineView::Private : public QObject { Q_OBJECT public: explicit Private(TimelineView *parent = nullptr); ~Private(); TimelineItem *calendarItemForIncidence(const Akonadi::Item &incidence); void insertIncidence(const Akonadi::Item &incidence); void insertIncidence(const Akonadi::Item &incidence, const QDate &day); void removeIncidence(const Akonadi::Item &incidence); public Q_SLOTS: // void overscale( KDGantt::View::Scale scale ); void itemSelected(const QModelIndex &index); void itemDoubleClicked(const QModelIndex &index); void itemChanged(QStandardItem *item); void contextMenuRequested(const QPoint &point); void newEventWithHint(const QDateTime &); void splitterMoved(); public: Akonadi::Item::List mSelectedItemList; KGantt::GraphicsView *mGantt = nullptr; QTreeWidget *mLeftView = nullptr; RowController *mRowController = nullptr; QMap<Akonadi::Collection::Id, TimelineItem *> mCalendarItemMap; QDate mStartDate, mEndDate; QDateTime mHintDate; private: TimelineView *const q; }; } // namespace EventViews #endif 07070100000047000041ED0000000200000002000000025F0BF3C900000000000000000000000000000000000000000000003700000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo07070100000048000081A40000000200000002000000015F0BF3C900007EFB000000000000000000000000000000000000004E00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/incidencetreemodel.cpp/* Copyright (c) 2012 Sérgio Martins <iamsergio@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "incidencetreemodel_p.h" #include "calendarview_debug.h" #include <AkonadiCore/EntityTreeModel> using namespace Akonadi; QDebug operator<<(QDebug s, const Node::Ptr &node); static void calculateDepth(const Node::Ptr &node) { Q_ASSERT(node); node->depth = node->parentNode ? 1 + node->parentNode->depth : 0; for (const Node::Ptr &child : qAsConst(node->directChilds)) { calculateDepth(child); } } // Desired ordering [...],3,2,1,0,-1 static bool reverseDepthLessThan(const Node::Ptr &node1, const Node::Ptr &node2) { return node1->depth > node2->depth; } // Desired ordering 0,1,2,3,[...],-1 static bool depthLessThan(const PreNode::Ptr &node1, const PreNode::Ptr &node2) { if (node1->depth == -1) { return false; } return node1->depth < node2->depth || node2->depth == -1; } static PreNode::List sortedPrenodes(const PreNode::List &nodes) { const int count = nodes.count(); QHash<QString, PreNode::Ptr> prenodeByUid; PreNode::List remainingNodes = nodes; while (prenodeByUid.count() < count) { const auto preSize = prenodeByUid.count(); // this saves us from infinit looping if the parent doesn't exist for (const PreNode::Ptr &node : nodes) { Q_ASSERT(node); const QString uid = node->incidence->instanceIdentifier(); const QString parentUid = node->incidence->relatedTo(); if (parentUid.isEmpty()) { // toplevel todo prenodeByUid.insert(uid, node); remainingNodes.removeAll(node); node->depth = 0; } else { if (prenodeByUid.contains(parentUid)) { node->depth = 1 + prenodeByUid.value(parentUid)->depth; remainingNodes.removeAll(node); prenodeByUid.insert(uid, node); } } } if (preSize == prenodeByUid.count()) { break; } } PreNode::List sorted = nodes; std::sort(sorted.begin(), sorted.end(), depthLessThan); return sorted; } IncidenceTreeModel::Private::Private(IncidenceTreeModel *qq, const QStringList &mimeTypes) : QObject() , m_mimeTypes(mimeTypes) , q(qq) { } int IncidenceTreeModel::Private::rowForNode(const Node::Ptr &node) const { // Returns it's row number const int row = node->parentNode ? node->parentNode->directChilds.indexOf(node) : m_toplevelNodeList.indexOf(node); Q_ASSERT(row != -1); return row; } void IncidenceTreeModel::Private::assert_and_dump(bool condition, const QString &message) { if (!condition) { qCCritical(CALENDARVIEW_LOG) << "This should never happen: " << message; dumpTree(); Q_ASSERT(false); } } void IncidenceTreeModel::Private::dumpTree() { for (const Node::Ptr &node : qAsConst(m_toplevelNodeList)) { qCDebug(CALENDARVIEW_LOG) << node; } } QModelIndex IncidenceTreeModel::Private::indexForNode(const Node::Ptr &node) const { if (!node) { return QModelIndex(); } const int row = node->parentNode ? node->parentNode->directChilds.indexOf(node) : m_toplevelNodeList.indexOf(node); Q_ASSERT(row != -1); return q->createIndex(row, 0, node.data()); } void IncidenceTreeModel::Private::reset(bool silent) { if (!silent) { q->beginResetModel(); } m_toplevelNodeList.clear(); m_nodeMap.clear(); m_itemByUid.clear(); m_waitingForParent.clear(); m_uidMap.clear(); if (q->sourceModel()) { const int sourceCount = q->sourceModel()->rowCount(); for (int i = 0; i < sourceCount; ++i) { PreNode::Ptr prenode = prenodeFromSourceRow(i); if (prenode && (m_mimeTypes.isEmpty() || m_mimeTypes.contains(prenode->incidence->mimeType()))) { insertNode(prenode, /**silent=*/ true); } } } if (!silent) { q->endResetModel(); } } void IncidenceTreeModel::Private::onHeaderDataChanged(Qt::Orientation orientation, int first, int last) { Q_EMIT q->headerDataChanged(orientation, first, last); } void IncidenceTreeModel::Private::onDataChanged(const QModelIndex &begin, const QModelIndex &end) { Q_ASSERT(begin.isValid()); Q_ASSERT(end.isValid()); Q_ASSERT(q->sourceModel()); Q_ASSERT(!begin.parent().isValid()); Q_ASSERT(!end.parent().isValid()); Q_ASSERT(begin.row() <= end.row()); const int first_row = begin.row(); const int last_row = end.row(); for (int i = first_row; i <= last_row; ++i) { QModelIndex sourceIndex = q->sourceModel()->index(i, 0); Q_ASSERT(sourceIndex.isValid()); QModelIndex index = q->mapFromSource(sourceIndex); // Index might be invalid if we filter by incidence type. if (index.isValid()) { Q_ASSERT(index.internalPointer()); // Did we this node change parent? If no, just Q_EMIT dataChanged(), if // yes, we must Q_EMIT rowsMoved(), so we see a visual effect in the view. Node *rawNode = reinterpret_cast<Node *>(index.internalPointer()); Node::Ptr node = m_uidMap.value(rawNode->uid); // Looks hackish but it's safe Q_ASSERT(node); Node::Ptr oldParentNode = node->parentNode; Akonadi::Item item = q->data(index, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); Q_ASSERT(item.isValid()); KCalendarCore::Incidence::Ptr incidence = !item.hasPayload<KCalendarCore::Incidence::Ptr>() ? KCalendarCore::Incidence::Ptr() : item.payload<KCalendarCore::Incidence::Ptr>(); if (!incidence) { qCCritical(CALENDARVIEW_LOG) << "Incidence shouldn't be invalid." << item.hasPayload() << item.id(); Q_ASSERT(false); return; } // An UID could have changed, update hashes! if (node->uid != incidence->instanceIdentifier()) { qCDebug(CALENDARVIEW_LOG) << "Incidence UID has changed" << node->uid << incidence->instanceIdentifier(); m_itemByUid.remove(node->uid); m_uidMap.remove(node->uid); node->uid = incidence->instanceIdentifier(); m_uidMap.insert(node->uid, node); } m_itemByUid.insert(incidence->instanceIdentifier(), item); Node::Ptr newParentNode; const QString newParentUid = incidence->relatedTo(); if (!newParentUid.isEmpty()) { Q_ASSERT(m_uidMap.contains(newParentUid)); newParentNode = m_uidMap.value(newParentUid); Q_ASSERT(newParentNode); } const bool parentChanged = newParentNode.data() != oldParentNode.data(); if (parentChanged) { const int fromRow = rowForNode(node); int toRow = -1; QModelIndex newParentIndex; // Calculate parameters for beginMoveRows() if (newParentNode) { newParentIndex = q->mapFromSource(newParentNode->sourceIndex); Q_ASSERT(newParentIndex.isValid()); toRow = newParentNode->directChilds.count(); } else { // New parent is 0, it's son of root now newParentIndex = QModelIndex(); toRow = m_toplevelNodeList.count(); } const bool res = q->beginMoveRows(/**fromParent*/ index.parent(), fromRow, fromRow, newParentIndex, toRow); Q_ASSERT(res); Q_UNUSED(res); // Now that beginmoveRows() was called, we can do the actual moving: if (newParentNode) { newParentNode->directChilds.append(node); // Add to new parent node->parentNode = newParentNode; if (oldParentNode) { oldParentNode->directChilds.remove(fromRow); // Remove from parent Q_ASSERT(oldParentNode->directChilds.indexOf(node) == -1); } else { m_toplevelNodeList.remove(fromRow); // Remove from root Q_ASSERT(m_toplevelNodeList.indexOf(node) == -1); } } else { // New parent is 0, it's son of root now m_toplevelNodeList.append(node); node->parentNode = Node::Ptr(); oldParentNode->directChilds.remove(fromRow); Q_ASSERT(oldParentNode->directChilds.indexOf(node) == -1); } q->endMoveRows(); // index is rotten after the move, retrieve it again index = indexForNode(node); Q_ASSERT(index.isValid()); if (newParentNode) { Q_EMIT q->indexChangedParent(index.parent()); } } else { Q_EMIT q->dataChanged(index, index); } } } } void IncidenceTreeModel::Private::onRowsAboutToBeInserted(const QModelIndex &parent, int, int) { // We are a reparenting proxy, the source proxy is flat Q_ASSERT(!parent.isValid()); Q_UNUSED(parent); // Nothing to do yet. We don't know if all the new incidences in this range belong to the same // parent yet. } PreNode::Ptr IncidenceTreeModel::Private::prenodeFromSourceRow(int row) const { PreNode::Ptr node = PreNode::Ptr(new PreNode()); node->sourceIndex = q->sourceModel()->index(row, 0, QModelIndex()); Q_ASSERT(node->sourceIndex.isValid()); Q_ASSERT(node->sourceIndex.model() == q->sourceModel()); const Akonadi::Item item = node->sourceIndex.data(EntityTreeModel::ItemRole).value<Akonadi::Item>(); if (!item.isValid()) { // It's a Collection, ignore that, we only want items. return PreNode::Ptr(); } node->item = item; node->incidence = item.payload<KCalendarCore::Incidence::Ptr>(); Q_ASSERT(node->incidence); return node; } void IncidenceTreeModel::Private::onRowsInserted(const QModelIndex &parent, int begin, int end) { //QElapsedTimer timer; //timer.start(); Q_ASSERT(!parent.isValid()); Q_UNUSED(parent); Q_ASSERT(begin <= end); PreNode::List nodes; for (int i = begin; i <= end; ++i) { PreNode::Ptr node = prenodeFromSourceRow(i); // if m_mimeTypes is empty, we ignore this feature if (!node || (!m_mimeTypes.isEmpty() && !m_mimeTypes.contains(node->incidence->mimeType()))) { continue; } nodes << node; } const PreNode::List sortedNodes = sortedPrenodes(nodes); for (const PreNode::Ptr &node : sortedNodes) { insertNode(node); } // view can now call KConfigViewStateSaver::restoreState(), to expand nodes. if (end > begin) { Q_EMIT q->batchInsertionFinished(); } //qCDebug(CALENDARVIEW_LOG) << "Took " << timer.elapsed() << " to insert " << end-begin+1; } void IncidenceTreeModel::Private::insertNode(const PreNode::Ptr &prenode, bool silent) { KCalendarCore::Incidence::Ptr incidence = prenode->incidence; Akonadi::Item item = prenode->item; Node::Ptr node(new Node()); node->sourceIndex = prenode->sourceIndex; node->id = item.id(); node->uid = incidence->instanceIdentifier(); m_itemByUid.insert(node->uid, item); //qCDebug(CALENDARVIEW_LOG) << "New node " << node.data() << node->uid << node->id; node->parentUid = incidence->relatedTo(); if (node->uid == node->parentUid) { qCWarning(CALENDARVIEW_LOG) << "Incidence with itself as parent!" << node->uid << "Akonadi item" << item.id() << "remoteId=" << item.remoteId(); node->parentUid.clear(); } if (m_uidMap.contains(node->uid)) { qCWarning(CALENDARVIEW_LOG) << "Duplicate incidence detected. File a bug against the resource. collection=" << item.storageCollectionId(); return; } Q_ASSERT(!m_nodeMap.contains(node->id)); m_uidMap.insert(node->uid, node); m_nodeMap.insert(item.id(), node); int rowToUse = -1; bool mustInsertIntoParent = false; const bool hasParent = !node->parentUid.isEmpty(); if (hasParent) { // We have a parent, did he arrive yet ? if (m_uidMap.contains(node->parentUid)) { node->parentNode = m_uidMap.value(node->parentUid); // We can only insert after beginInsertRows(), because it affects rowCounts mustInsertIntoParent = true; rowToUse = node->parentNode->directChilds.count(); } else { // Parent unknown, we are orphan for now Q_ASSERT(!m_waitingForParent.contains(node->parentUid, node)); m_waitingForParent.insert(node->parentUid, node); } } if (!node->parentNode) { rowToUse = m_toplevelNodeList.count(); } // Lets insert the row: const QModelIndex &parent = indexForNode(node->parentNode); if (!silent) { q->beginInsertRows(parent, rowToUse, rowToUse); } if (!node->parentNode) { m_toplevelNodeList.append(node); } if (mustInsertIntoParent) { node->parentNode->directChilds.append(node); } if (!silent) { q->endInsertRows(); } // Are we a parent? if (m_waitingForParent.contains(node->uid)) { Q_ASSERT(m_waitingForParent.count(node->uid) > 0); const QList<Node::Ptr> childs = m_waitingForParent.values(node->uid); m_waitingForParent.remove(node->uid); Q_ASSERT(!childs.isEmpty()); for (const Node::Ptr &child : childs) { const int fromRow = m_toplevelNodeList.indexOf(child); Q_ASSERT(fromRow != -1); const QModelIndex toParent = indexForNode(node); Q_ASSERT(toParent.isValid()); Q_ASSERT(toParent.model() == q); //const int toRow = node->directChilds.count(); if (!silent) { //const bool res = q->beginMoveRows( /**fromParent*/QModelIndex(), fromRow, // fromRow, toParent, toRow ); //Q_EMIT q->layoutAboutToBeChanged(); q->beginResetModel(); //Q_ASSERT( res ); } child->parentNode = node; node->directChilds.append(child); m_toplevelNodeList.remove(fromRow); if (!silent) { //q->endMoveRows(); q->endResetModel(); //Q_EMIT q->layoutChanged(); } } } } // Sorts childs first parents last Node::List IncidenceTreeModel::Private::sorted(const Node::List &nodes) const { if (nodes.isEmpty()) { return nodes; } // Initialize depths for (const Node::Ptr &topLevelNode : qAsConst(m_toplevelNodeList)) { calculateDepth(topLevelNode); } Node::List sorted = nodes; std::sort(sorted.begin(), sorted.end(), reverseDepthLessThan); return sorted; } void IncidenceTreeModel::Private::onRowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end) { //QElapsedTimer timer; //timer.start(); Q_ASSERT(!parent.isValid()); Q_UNUSED(parent); Q_ASSERT(begin <= end); // First, gather nodes to remove Node::List nodesToRemove; for (int i = begin; i <= end; ++i) { QModelIndex sourceIndex = q->sourceModel()->index(i, 0, QModelIndex()); Q_ASSERT(sourceIndex.isValid()); Q_ASSERT(sourceIndex.model() == q->sourceModel()); const Akonadi::Item::Id id = sourceIndex.data(EntityTreeModel::ItemIdRole).toLongLong(); Q_ASSERT(id != -1); if (!m_nodeMap.contains(id)) { // We don't know about this one because we're ignoring it's mime type. Q_ASSERT(m_mimeTypes.count() != 3); continue; } Node::Ptr node = m_nodeMap.value(id); Q_ASSERT(node->id == id); nodesToRemove << node; } // We want to remove childs first, to avoid row moving const Node::List nodesToRemoveSorted = sorted(nodesToRemove); for (const Node::Ptr &node : nodesToRemoveSorted) { // Go ahead and remove it now. We don't do it in ::onRowsRemoved(), because // while unparenting childs with moveRows() the view might call data() on the // item that is already removed from ETM. removeNode(node); //qCDebug(CALENDARVIEW_LOG) << "Just removed a node, here's the tree"; //dumpTree(); } m_removedNodes.clear(); //qCDebug(CALENDARVIEW_LOG) << "Took " << timer.elapsed() << " to remove " << end-begin+1; } void IncidenceTreeModel::Private::removeNode(const Node::Ptr &node) { Q_ASSERT(node); //qCDebug(CALENDARVIEW_LOG) << "Dealing with parent: " << node->id << node.data() // << node->uid << node->directChilds.count() << indexForNode( node ); // First, unparent the children if (!node->directChilds.isEmpty()) { const Node::List childs = node->directChilds; const QModelIndex fromParent = indexForNode(node); Q_ASSERT(fromParent.isValid()); // const int firstSourceRow = 0; // const int lastSourceRow = node->directChilds.count() - 1; //const int toRow = m_toplevelNodeList.count(); //q->beginMoveRows( fromParent, firstSourceRow, lastSourceRow, // /**toParent is root*/QModelIndex(), toRow ); q->beginResetModel(); node->directChilds.clear(); for (const Node::Ptr &child : childs) { //qCDebug(CALENDARVIEW_LOG) << "Dealing with child: " << child.data() << child->uid; m_toplevelNodeList.append(child); child->parentNode = Node::Ptr(); m_waitingForParent.insert(node->uid, child); } //q->endMoveRows(); q->endResetModel(); } const QModelIndex parent = indexForNode(node->parentNode); const int rowToRemove = rowForNode(node); // Now remove the row Q_ASSERT(!(parent.isValid() && parent.model() != q)); q->beginRemoveRows(parent, rowToRemove, rowToRemove); m_itemByUid.remove(node->uid); if (parent.isValid()) { node->parentNode->directChilds.remove(rowToRemove); node->parentNode = Node::Ptr(); } else { m_toplevelNodeList.remove(rowToRemove); } if (!node->parentUid.isEmpty()) { m_waitingForParent.remove(node->parentUid, node); } m_uidMap.remove(node->uid); m_nodeMap.remove(node->id); q->endRemoveRows(); m_removedNodes << node.data(); } void IncidenceTreeModel::Private::onRowsRemoved(const QModelIndex &parent, int begin, int end) { Q_UNUSED(parent); Q_UNUSED(begin); Q_UNUSED(end); // Nothing to do here, see comment on ::onRowsAboutToBeRemoved() } void IncidenceTreeModel::Private::onModelAboutToBeReset() { q->beginResetModel(); } void IncidenceTreeModel::Private::onModelReset() { reset(/**silent=*/ false); q->endResetModel(); } void IncidenceTreeModel::Private::onLayoutAboutToBeChanged() { Q_ASSERT(q->persistentIndexList().isEmpty()); Q_EMIT q->layoutAboutToBeChanged(); } void IncidenceTreeModel::Private::onLayoutChanged() { reset(/**silent=*/ true); Q_ASSERT(q->persistentIndexList().isEmpty()); Q_EMIT q->layoutChanged(); } void IncidenceTreeModel::Private::onRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int) { // Not implemented yet Q_ASSERT(false); } void IncidenceTreeModel::Private::setSourceModel(QAbstractItemModel *model) { q->beginResetModel(); if (q->sourceModel()) { disconnect(q->sourceModel(), &IncidenceTreeModel::dataChanged, this, &IncidenceTreeModel::Private::onDataChanged); disconnect(q->sourceModel(), &IncidenceTreeModel::headerDataChanged, this, &IncidenceTreeModel::Private::onHeaderDataChanged); disconnect(q->sourceModel(), &IncidenceTreeModel::rowsInserted, this, &IncidenceTreeModel::Private::onRowsInserted); disconnect(q->sourceModel(), &IncidenceTreeModel::rowsRemoved, this, &IncidenceTreeModel::Private::onRowsRemoved); disconnect(q->sourceModel(), &IncidenceTreeModel::rowsMoved, this, &IncidenceTreeModel::Private::onRowsMoved); disconnect(q->sourceModel(), &IncidenceTreeModel::rowsAboutToBeInserted, this, &IncidenceTreeModel::Private::onRowsAboutToBeInserted); disconnect(q->sourceModel(), &IncidenceTreeModel::rowsAboutToBeRemoved, this, &IncidenceTreeModel::Private::onRowsAboutToBeRemoved); disconnect(q->sourceModel(), &IncidenceTreeModel::modelAboutToBeReset, this, &IncidenceTreeModel::Private::onModelAboutToBeReset); disconnect(q->sourceModel(), &IncidenceTreeModel::modelReset, this, &IncidenceTreeModel::Private::onModelReset); disconnect(q->sourceModel(), &IncidenceTreeModel::layoutAboutToBeChanged, this, &IncidenceTreeModel::Private::onLayoutAboutToBeChanged); disconnect(q->sourceModel(), &IncidenceTreeModel::layoutChanged, this, &IncidenceTreeModel::Private::onLayoutChanged); } q->QAbstractProxyModel::setSourceModel(model); if (q->sourceModel()) { connect(q->sourceModel(), &IncidenceTreeModel::dataChanged, this, &IncidenceTreeModel::Private::onDataChanged); connect(q->sourceModel(), &IncidenceTreeModel::headerDataChanged, this, &IncidenceTreeModel::Private::onHeaderDataChanged); connect(q->sourceModel(), &IncidenceTreeModel::rowsAboutToBeInserted, this, &IncidenceTreeModel::Private::onRowsAboutToBeInserted); connect(q->sourceModel(), &IncidenceTreeModel::rowsInserted, this, &IncidenceTreeModel::Private::onRowsInserted); connect(q->sourceModel(), &IncidenceTreeModel::rowsAboutToBeRemoved, this, &IncidenceTreeModel::Private::onRowsAboutToBeRemoved); connect(q->sourceModel(), &IncidenceTreeModel::rowsRemoved, this, &IncidenceTreeModel::Private::onRowsRemoved); connect(q->sourceModel(), &IncidenceTreeModel::rowsMoved, this, &IncidenceTreeModel::Private::onRowsMoved); connect(q->sourceModel(), &IncidenceTreeModel::modelAboutToBeReset, this, &IncidenceTreeModel::Private::onModelAboutToBeReset); connect(q->sourceModel(), &IncidenceTreeModel::modelReset, this, &IncidenceTreeModel::Private::onModelReset); connect(q->sourceModel(), &IncidenceTreeModel::layoutAboutToBeChanged, this, &IncidenceTreeModel::Private::onLayoutAboutToBeChanged); connect(q->sourceModel(), &IncidenceTreeModel::layoutChanged, this, &IncidenceTreeModel::Private::onLayoutChanged); } reset(/**silent=*/ true); q->endResetModel(); } IncidenceTreeModel::IncidenceTreeModel(QObject *parent) : QAbstractProxyModel(parent) , d(new Private(this, QStringList())) { setObjectName(QStringLiteral("IncidenceTreeModel")); } IncidenceTreeModel::IncidenceTreeModel(const QStringList &mimeTypes, QObject *parent) : QAbstractProxyModel(parent) , d(new Private(this, mimeTypes)) { setObjectName(QStringLiteral("IncidenceTreeModel")); } IncidenceTreeModel::~IncidenceTreeModel() { delete d; } QVariant IncidenceTreeModel::data(const QModelIndex &index, int role) const { Q_ASSERT(index.isValid()); if (!index.isValid() || !sourceModel()) { return QVariant(); } QModelIndex sourceIndex = mapToSource(index); Q_ASSERT(sourceIndex.isValid()); return sourceModel()->data(sourceIndex, role); } int IncidenceTreeModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { Q_ASSERT(parent.model() == this); Node *parentNode = reinterpret_cast<Node *>(parent.internalPointer()); Q_ASSERT(parentNode); d->assert_and_dump(!d->m_removedNodes.contains(parentNode), QString::number((quintptr)parentNode, 16) + QLatin1String(" was already deleted")); const int count = parentNode->directChilds.count(); return count; } return d->m_toplevelNodeList.count(); } int IncidenceTreeModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) { Q_ASSERT(parent.model() == this); } return sourceModel() ? sourceModel()->columnCount() : 1; } void IncidenceTreeModel::setSourceModel(QAbstractItemModel *model) { if (model == sourceModel()) { return; } d->setSourceModel(model); } QModelIndex IncidenceTreeModel::mapFromSource(const QModelIndex &sourceIndex) const { if (!sourceIndex.isValid()) { qCWarning(CALENDARVIEW_LOG) << "IncidenceTreeModel::mapFromSource() source index is invalid"; // Q_ASSERT( false ); return QModelIndex(); } if (!sourceModel()) { return QModelIndex(); } Q_ASSERT(sourceIndex.column() < sourceModel()->columnCount()); Q_ASSERT(sourceModel() == sourceIndex.model()); const Akonadi::Item::Id id = sourceIndex.data(Akonadi::EntityTreeModel::ItemIdRole).toLongLong(); if (id == -1 || !d->m_nodeMap.contains(id)) { return QModelIndex(); } const Node::Ptr node = d->m_nodeMap.value(id); Q_ASSERT(node); return d->indexForNode(node); } QModelIndex IncidenceTreeModel::mapToSource(const QModelIndex &proxyIndex) const { if (!proxyIndex.isValid() || !sourceModel()) { return QModelIndex(); } Q_ASSERT(proxyIndex.column() < columnCount()); Q_ASSERT(proxyIndex.internalPointer()); Q_ASSERT(proxyIndex.model() == this); Node *node = reinterpret_cast<Node *>(proxyIndex.internalPointer()); /* This code is slow, using a persistent model index instead. QModelIndexList indexes = EntityTreeModel::modelIndexesForItem( sourceModel(), Akonadi::Item( node->id ) ); if ( indexes.isEmpty() ) { Q_ASSERT( sourceModel() ); qCCritical(CALENDARVIEW_LOG) << "IncidenceTreeModel::mapToSource() no indexes." << proxyIndex << node->id << "; source.rowCount() = " << sourceModel()->rowCount() << "; source=" << sourceModel() << "rowCount()" << rowCount(); Q_ASSERT( false ); return QModelIndex(); } QModelIndex index = indexes.first();*/ QModelIndex index = node->sourceIndex; if (!index.isValid()) { qCWarning(CALENDARVIEW_LOG) << "IncidenceTreeModel::mapToSource(): sourceModelIndex is invalid"; Q_ASSERT(false); return QModelIndex(); } Q_ASSERT(index.model() == sourceModel()); return index.sibling(index.row(), proxyIndex.column()); } QModelIndex IncidenceTreeModel::parent(const QModelIndex &child) const { if (!child.isValid()) { qCWarning(CALENDARVIEW_LOG) << "IncidenceTreeModel::parent(): child is invalid"; Q_ASSERT(false); return QModelIndex(); } Q_ASSERT(child.model() == this); Q_ASSERT(child.internalPointer()); Node *childNode = reinterpret_cast<Node *>(child.internalPointer()); if (d->m_removedNodes.contains(childNode)) { qCWarning(CALENDARVIEW_LOG) << "IncidenceTreeModel::parent() Node already removed."; return QModelIndex(); } if (!childNode->parentNode) { return QModelIndex(); } const QModelIndex parentIndex = d->indexForNode(childNode->parentNode); if (!parentIndex.isValid()) { qCWarning(CALENDARVIEW_LOG) << "IncidenceTreeModel::parent(): proxyModelIndex is invalid."; Q_ASSERT(false); return QModelIndex(); } Q_ASSERT(parentIndex.model() == this); Q_ASSERT(childNode->parentNode.data()); // Parent is always at row 0 return parentIndex; } QModelIndex IncidenceTreeModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || row >= rowCount(parent)) { // This is ok apparently /*qCWarning(CALENDARVIEW_LOG) << "IncidenceTreeModel::index() parent.isValid()" << parent.isValid() << "; row=" << row << "; column=" << column << "; rowCount() = " << rowCount( parent ); */ // Q_ASSERT( false ); return QModelIndex(); } Q_ASSERT(column >= 0); Q_ASSERT(column < columnCount()); if (parent.isValid()) { Q_ASSERT(parent.model() == this); Q_ASSERT(parent.internalPointer()); Node *parentNode = reinterpret_cast<Node *>(parent.internalPointer()); if (row >= parentNode->directChilds.count()) { qCCritical(CALENDARVIEW_LOG) << "IncidenceTreeModel::index() row=" << row << "; column=" << column; Q_ASSERT(false); return QModelIndex(); } return createIndex(row, column, parentNode->directChilds.at(row).data()); } else { Q_ASSERT(row < d->m_toplevelNodeList.count()); Node::Ptr node = d->m_toplevelNodeList.at(row); Q_ASSERT(node); return createIndex(row, column, node.data()); } } bool IncidenceTreeModel::hasChildren(const QModelIndex &parent) const { if (parent.isValid()) { Q_ASSERT(parent.column() < columnCount()); if (parent.column() != 0) { // Indexes at column >0 don't have parent, says Qt documentation return false; } Node *parentNode = reinterpret_cast<Node *>(parent.internalPointer()); Q_ASSERT(parentNode); return !parentNode->directChilds.isEmpty(); } else { return !d->m_toplevelNodeList.isEmpty(); } } Akonadi::Item IncidenceTreeModel::item(const QString &uid) const { Akonadi::Item item; if (uid.isEmpty()) { qCWarning(CALENDARVIEW_LOG) << "Called with an empty uid"; } else { if (d->m_itemByUid.contains(uid)) { item = d->m_itemByUid.value(uid); } else { qCWarning(CALENDARVIEW_LOG) << "There's no incidence with uid " << uid; } } return item; } QDebug operator<<(QDebug s, const Node::Ptr &node) { Q_ASSERT(node); static int level = 0; ++level; QString padding = QString(level - 1, QLatin1Char(' ')); s << padding + QLatin1String("node") << node.data() << QStringLiteral(";uid=") << node->uid << QStringLiteral(";id=") << node->id << QStringLiteral(";parentUid=") << node->parentUid << QStringLiteral(";parentNode=") << (void *)(node->parentNode.data()) << '\n'; for (const Node::Ptr &child : qAsConst(node->directChilds)) { s << child; } --level; return s; } 07070100000049000081A40000000200000002000000015F0BF3C900000EF7000000000000000000000000000000000000004C00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/incidencetreemodel.h/* Copyright (c) 2012 Sérgio Martins <iamsergio@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef INCIDENCE_TREEMODEL_H #define INCIDENCE_TREEMODEL_H #include <AkonadiCore/Item> #include <QAbstractProxyModel> class IncidenceTreeModel : public QAbstractProxyModel { Q_OBJECT public: /** * Constructs a new IncidenceTreeModel. */ explicit IncidenceTreeModel(QObject *parent = nullptr); /** * Constructs a new IncidenceTreeModel which will only show incidences of * type @p mimeTypes. Common use case is a to-do tree. * * This constructor is offered for performance reasons. The filtering has * zero overhead, and we avoid stacking mime type filter proxy models. * * If you're more concerned about clean design than performance, use the default * constructor and stack a Akonadi::EntityMimeTypeFilterModel on top of this one. */ explicit IncidenceTreeModel(const QStringList &mimeTypes, QObject *parent = nullptr); ~IncidenceTreeModel() override; Q_REQUIRED_RESULT int rowCount(const QModelIndex &parent = QModelIndex()) const override; Q_REQUIRED_RESULT int columnCount(const QModelIndex &parent = QModelIndex()) const override; Q_REQUIRED_RESULT QVariant data(const QModelIndex &index, int role) const override; Q_REQUIRED_RESULT QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; Q_REQUIRED_RESULT QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override; Q_REQUIRED_RESULT QModelIndex mapToSource(const QModelIndex &proxyIndex) const override; Q_REQUIRED_RESULT QModelIndex parent(const QModelIndex &child) const override; void setSourceModel(QAbstractItemModel *sourceModel) override; Q_REQUIRED_RESULT bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; /** * Returns the akonadi item containing the incidence with @p incidenceUid. */ Q_REQUIRED_RESULT Akonadi::Item item(const QString &incidenceUid) const; Q_SIGNALS: /** * This signal is emitted whenever an index changes parent. * The view can then expand the parent if desired. * This is better than the view waiting for "rows moved" signals because those * signals are also sent when the model is initially populated. */ void indexChangedParent(const QModelIndex &index); /** * Signals that we finished doing a batch of insertions. * * One rowsInserted() signal from the ETM, will make IncidenceTreeModel generate * several rowsInserted(), layoutChanged() or rowsMoved() signals. * * A tree view can use this signal to know when to call KConfigViewStateSaver::restore() * to restore expansion states. Listening to rowsInserted() signals would be a * performance problem. */ void batchInsertionFinished(); private: class Private; Private *const d; }; #endif 0707010000004A000081A40000000200000002000000015F0BF3C900000EF7000000000000000000000000000000000000004E00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/incidencetreemodel_p.h/* Copyright (c) 2012 Sérgio Martins <iamsergio@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef INCIDENCE_TREEMODEL_P_H #define INCIDENCE_TREEMODEL_P_H #include "incidencetreemodel.h" #include <AkonadiCore/Item> #include <QHash> #include <QObject> #include <QVector> #include <QModelIndex> #include <QSharedPointer> #include <QStringList> #include <QPersistentModelIndex> #include <KCalendarCore/Incidence> typedef QString Uid; typedef QString ParentUid; struct Node { typedef QSharedPointer<Node> Ptr; typedef QMap<Akonadi::Item::Id, Ptr> Map; typedef QVector<Ptr> List; QPersistentModelIndex sourceIndex; // because ETM::modelIndexesForItem is so slow Akonadi::Item::Id id; Node::Ptr parentNode; QString parentUid; QString uid; List directChilds; int depth; }; /** Just a struct to contain some data before we create the node */ struct PreNode { typedef QSharedPointer<PreNode> Ptr; typedef QVector<Ptr> List; KCalendarCore::Incidence::Ptr incidence; QPersistentModelIndex sourceIndex; Akonadi::Item item; int depth; PreNode() : depth(-1) { } }; class IncidenceTreeModel::Private : public QObject { Q_OBJECT public: Private(IncidenceTreeModel *qq, const QStringList &mimeTypes); void reset(bool silent = false); void insertNode(const PreNode::Ptr &node, bool silent = false); void insertNode(const QModelIndex &sourceIndex, bool silent = false); void removeNode(const Node::Ptr &node); QModelIndex indexForNode(const Node::Ptr &node) const; int rowForNode(const Node::Ptr &node) const; bool indexBeingRemoved(const QModelIndex &) const; // Is it being removed? void dumpTree(); void assert_and_dump(bool condition, const QString &message); Node::List sorted(const Node::List &nodes) const; PreNode::Ptr prenodeFromSourceRow(int sourceRow) const; void setSourceModel(QAbstractItemModel *model); public: Node::Map m_nodeMap; Node::List m_toplevelNodeList; QHash<Uid, Node::Ptr> m_uidMap; QHash<Uid, Akonadi::Item> m_itemByUid; QMultiHash<ParentUid, Node::Ptr> m_waitingForParent; QList<Node *> m_removedNodes; const QStringList m_mimeTypes; private Q_SLOTS: void onHeaderDataChanged(Qt::Orientation orientation, int first, int last); void onDataChanged(const QModelIndex &begin, const QModelIndex &end); void onRowsAboutToBeInserted(const QModelIndex &parent, int begin, int end); void onRowsInserted(const QModelIndex &parent, int begin, int end); void onRowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end); void onRowsRemoved(const QModelIndex &parent, int begin, int end); void onRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int); void onModelAboutToBeReset(); void onModelReset(); void onLayoutAboutToBeChanged(); void onLayoutChanged(); private: IncidenceTreeModel *q; }; #endif 0707010000004B000081A40000000200000002000000015F0BF3C9000031DF000000000000000000000000000000000000004900000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/tododelegates.cpp/* This file is part of KOrganizer. Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "tododelegates.h" #include "todomodel.h" #include "todoviewview.h" #include <AkonadiWidgets/TagSelectionComboBox> #include <Akonadi/Calendar/ETMCalendar> #include <CalendarSupport/CategoryConfig> #include <CalendarSupport/CategoryHierarchyReader> #include <KCalendarCore/CalFilter> #include <KDateComboBox> #include <QApplication> #include <QPainter> #include <QTextDocument> #include <QToolTip> // ---------------- COMPLETION DELEGATE -------------------------- // --------------------------------------------------------------- TodoCompleteDelegate::TodoCompleteDelegate(QObject *parent) : QStyledItemDelegate(parent) { } void TodoCompleteDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter); if (index.data(Qt::EditRole).toInt() > 0) { bool isEditing = false; TodoViewView *view = qobject_cast<TodoViewView *>(parent()); if (view) { isEditing = view->isEditing(index); } // TODO QTreeView does not set State_Editing. Qt task id 205051 // should be fixed with Qt 4.5, but wasn't. According to the // task tracker the fix arrives in "Some future release". if (!(opt.state & QStyle::State_Editing) && !isEditing) { QStyleOptionProgressBar pbOption; pbOption.QStyleOption::operator=(option); initStyleOptionProgressBar(&pbOption, index); style->drawControl(QStyle::CE_ProgressBar, &pbOption, painter); } } } QSize TodoCompleteDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); QStyleOptionProgressBar pbOption; pbOption.QStyleOption::operator=(option); initStyleOptionProgressBar(&pbOption, index); return style->sizeFromContents(QStyle::CT_ProgressBar, &pbOption, QSize(), opt.widget); } void TodoCompleteDelegate::initStyleOptionProgressBar( QStyleOptionProgressBar *option, const QModelIndex &index) const { option->rect.adjust(0, 1, 0, -1); option->maximum = 100; option->minimum = 0; option->progress = index.data().toInt(); option->text = index.data().toString() + QChar::fromLatin1('%'); option->textAlignment = Qt::AlignCenter; option->textVisible = true; } QWidget *TodoCompleteDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(option); Q_UNUSED(index); TodoCompleteSlider *slider = new TodoCompleteSlider(parent); slider->setRange(0, 100); slider->setOrientation(Qt::Horizontal); return slider; } void TodoCompleteDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QSlider *slider = static_cast<QSlider *>(editor); slider->setValue(index.data(Qt::EditRole).toInt()); } void TodoCompleteDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QSlider *slider = static_cast<QSlider *>(editor); model->setData(index, slider->value()); } void TodoCompleteDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); editor->setGeometry(option.rect); } TodoCompleteSlider::TodoCompleteSlider(QWidget *parent) : QSlider(parent) { connect(this, &TodoCompleteSlider::valueChanged, this, &TodoCompleteSlider::updateTip); } void TodoCompleteSlider::updateTip(int value) { QPoint p; p.setY(height() / 2); p.setX(style()->sliderPositionFromValue(minimum(), maximum(), value, width())); const QString text = QStringLiteral("%1%").arg(value); QToolTip::showText(mapToGlobal(p), text, this); } // ---------------- PRIORITY DELEGATE ---------------------------- // --------------------------------------------------------------- TodoPriorityDelegate::TodoPriorityDelegate(QObject *parent) : QStyledItemDelegate(parent) { } QWidget *TodoPriorityDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(option); Q_UNUSED(index); QComboBox *combo = new QComboBox(parent); combo->addItem(i18nc("@action:inmenu Unspecified priority", "unspecified")); combo->addItem(i18nc("@action:inmenu highest priority", "1 (highest)")); combo->addItem(i18nc("@action:inmenu", "2")); combo->addItem(i18nc("@action:inmenu", "3")); combo->addItem(i18nc("@action:inmenu", "4")); combo->addItem(i18nc("@action:inmenu medium priority", "5 (medium)")); combo->addItem(i18nc("@action:inmenu", "6")); combo->addItem(i18nc("@action:inmenu", "7")); combo->addItem(i18nc("@action:inmenu", "8")); combo->addItem(i18nc("@action:inmenu lowest priority", "9 (lowest)")); return combo; } void TodoPriorityDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QComboBox *combo = static_cast<QComboBox *>(editor); combo->setCurrentIndex(index.data(Qt::EditRole).toInt()); } void TodoPriorityDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QComboBox *combo = static_cast<QComboBox *>(editor); model->setData(index, combo->currentIndex()); } void TodoPriorityDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); editor->setGeometry(option.rect); } // ---------------- DUE DATE DELEGATE ---------------------------- // --------------------------------------------------------------- TodoDueDateDelegate::TodoDueDateDelegate(QObject *parent) : QStyledItemDelegate(parent) { } QWidget *TodoDueDateDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(option); Q_UNUSED(index); KDateComboBox *dateEdit = new KDateComboBox(parent); return dateEdit; } void TodoDueDateDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { KDateComboBox *dateEdit = static_cast<KDateComboBox *>(editor); dateEdit->setDate(index.data(Qt::EditRole).toDate()); } void TodoDueDateDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { KDateComboBox *dateEdit = static_cast<KDateComboBox *>(editor); model->setData(index, dateEdit->date()); } void TodoDueDateDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); editor->setGeometry(QStyle::alignedRect(QApplication::layoutDirection(), Qt::AlignCenter, editor->size(), option.rect)); } // ---------------- CATEGORIES DELEGATE -------------------------- // --------------------------------------------------------------- TodoCategoriesDelegate::TodoCategoriesDelegate(QObject *parent) : QStyledItemDelegate(parent) , mCalendar(nullptr) { } QWidget *TodoCategoriesDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(option); Q_UNUSED(index); return new Akonadi::TagSelectionComboBox(parent); } void TodoCategoriesDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { auto *combo = static_cast<Akonadi::TagSelectionComboBox *>(editor); combo->setSelection(index.data(Qt::EditRole).toStringList()); } void TodoCategoriesDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { auto *combo = static_cast<Akonadi::TagSelectionComboBox *>(editor); model->setData(index, combo->selectionNames()); } void TodoCategoriesDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); editor->setGeometry(option.rect); } void TodoCategoriesDelegate::setCalendar(const Akonadi::ETMCalendar::Ptr &cal) { mCalendar = cal; } // ---------------- RICH TEXT DELEGATE --------------------------- // --------------------------------------------------------------- TodoRichTextDelegate::TodoRichTextDelegate(QObject *parent) : QStyledItemDelegate(parent) { m_textDoc = new QTextDocument(this); } void TodoRichTextDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (index.data(TodoModel::IsRichTextRole).toBool()) { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); const QWidget *widget = opt.widget; QStyle *style = widget ? widget->style() : QApplication::style(); QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &opt, widget); // draw the item without text opt.text.clear(); style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget); // draw the text (rich text) QPalette::ColorGroup cg = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) { cg = QPalette::Inactive; } if (opt.state & QStyle::State_Selected) { painter->setPen( QPen(opt.palette.brush(cg, QPalette::HighlightedText), 0)); } else { painter->setPen( QPen(opt.palette.brush(cg, QPalette::Text), 0)); } if (opt.state & QStyle::State_Editing) { painter->setPen(QPen(opt.palette.brush(cg, QPalette::Text), 0)); painter->drawRect(textRect.adjusted(0, 0, -1, -1)); } m_textDoc->setHtml(index.data().toString()); painter->save(); painter->translate(textRect.topLeft()); QRect tmpRect = textRect; tmpRect.moveTo(0, 0); m_textDoc->setTextWidth(tmpRect.width()); m_textDoc->drawContents(painter, tmpRect); painter->restore(); } else { // align the text at top so that when it has more than two lines // it will just cut the extra lines out instead of aligning centered vertically QStyleOptionViewItem copy = option; copy.displayAlignment = Qt::AlignLeft | Qt::AlignTop; QStyledItemDelegate::paint(painter, copy, index); } } QSize TodoRichTextDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QSize ret = QStyledItemDelegate::sizeHint(option, index); if (index.data(TodoModel::IsRichTextRole).toBool()) { m_textDoc->setHtml(index.data().toString()); ret = ret.expandedTo(m_textDoc->size().toSize()); } // limit height to max. 2 lines // TODO add graphical hint when truncating! make configurable height? if (ret.height() > option.fontMetrics.height() * 2) { ret.setHeight(option.fontMetrics.height() * 2); } // This row might not have a checkbox, so give it more height so it appears the same size as other rows. const int checkboxHeight = QApplication::style()->sizeFromContents(QStyle::CT_CheckBox, &option, QSize()).height(); return QSize(ret.width(), qMax(ret.height(), checkboxHeight)); } 0707010000004C000081A40000000200000002000000015F0BF3C9000015B8000000000000000000000000000000000000004700000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/tododelegates.h/* This file is part of KOrganizer. Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef CALENDARVIEWS_TODODELEGATES_H #define CALENDARVIEWS_TODODELEGATES_H #include <Akonadi/Calendar/ETMCalendar> #include <QStyledItemDelegate> class QPainter; class QSize; class QStyleOptionViewItem; class QTextDocument; /** This delegate is responsible for displaying progress bars for the completion status of indivitual todos. It also provides a slider to change the completion status of the todo when in editing mode. @author Thomas Thrainer */ class TodoCompleteDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit TodoCompleteDelegate(QObject *parent = nullptr); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setEditorData(QWidget *editor, const QModelIndex &index) const override; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; private: void initStyleOptionProgressBar(QStyleOptionProgressBar *option, const QModelIndex &index) const; }; class TodoCompleteSlider : public QSlider { Q_OBJECT public: explicit TodoCompleteSlider(QWidget *parent); private Q_SLOTS: void updateTip(int value); }; /** This delegate is responsible for displaying the priority of todos. It also provides a combo box to change the priority of the todo when in editing mode. @author Thomas Thrainer */ class TodoPriorityDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit TodoPriorityDelegate(QObject *parent = nullptr); QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setEditorData(QWidget *editor, const QModelIndex &index) const override; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; }; /** This delegate is responsible for displaying the due date of todos. It also provides a combo box to change the due date of the todo when in editing mode. @author Thomas Thrainer */ class TodoDueDateDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit TodoDueDateDelegate(QObject *parent = nullptr); QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setEditorData(QWidget *editor, const QModelIndex &index) const override; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; }; /** This delegate is responsible for displaying the categories of todos. It also provides a combo box to change the categories of the todo when in editing mode. @author Thomas Thrainer */ class TodoCategoriesDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit TodoCategoriesDelegate(QObject *parent = nullptr); QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setEditorData(QWidget *editor, const QModelIndex &index) const override; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setCalendar(const Akonadi::ETMCalendar::Ptr &cal); private: Akonadi::ETMCalendar::Ptr mCalendar; }; /** This delegate is responsible for displaying possible rich text elements of a todo. That's the summary and the description. @author Thomas Thrainer */ class TodoRichTextDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit TodoRichTextDelegate(QObject *parent = nullptr); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; private: QTextDocument *m_textDoc = nullptr; }; #endif 0707010000004D000081A40000000200000002000000015F0BF3C900007D90000000000000000000000000000000000000004500000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/todomodel.cpp/* Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> Copyright (c) 2012 Sérgio Martins <iamsergio@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "todomodel_p.h" #include "incidencetreemodel.h" #include <CalendarSupport/Utils> #include <CalendarSupport/KCalPrefs> #include <KLocalizedString> #include <KCalendarCore/Todo> #include <KCalendarCore/Event> #include <KCalendarCore/Attachment> #include <KCalUtils/IncidenceFormatter> #include <KEmailAddress> #include <KCalUtils/DndFactory> #include <KCalUtils/ICalDrag> #include <KCalUtils/VCalDrag> #include <Akonadi/Calendar/ETMCalendar> #include <KMessageBox> #include "calendarview_debug.h" #include <QMimeData> #include <QIcon> struct SourceModelIndex { SourceModelIndex(int _r, int _c, void *_p, QAbstractItemModel *_m) : r(_r) , c(_c) , p(_p) , m(_m) { } operator QModelIndex() { return reinterpret_cast<QModelIndex &>(*this); } int r, c; void *p; const QAbstractItemModel *m = nullptr; }; static bool isDueToday(const KCalendarCore::Todo::Ptr &todo) { return !todo->isCompleted() && todo->dtDue().date() == QDate::currentDate(); } TodoModel::Private::Private(const EventViews::PrefsPtr &preferences, TodoModel *qq) : QObject() , m_changer(nullptr) , m_preferences(preferences) , q(qq) { } Akonadi::Item TodoModel::Private::findItemByUid(const QString &uid, const QModelIndex &parent) const { Q_ASSERT(!uid.isEmpty()); IncidenceTreeModel *treeModel = qobject_cast<IncidenceTreeModel *>(q->sourceModel()); if (treeModel) { // O(1) Shortcut return treeModel->item(uid); } Akonadi::Item item; const int count = q->rowCount(parent); for (int i = 0; i < count; ++i) { const QModelIndex currentIndex = q->index(i, 0, parent); Q_ASSERT(currentIndex.isValid()); item = q->data(currentIndex, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); if (item.isValid()) { return item; } else { item = findItemByUid(uid, currentIndex); if (item.isValid()) { return item; } } } return item; } void TodoModel::Private::onDataChanged(const QModelIndex &begin, const QModelIndex &end) { Q_ASSERT(begin.isValid()); Q_ASSERT(end.isValid()); const QModelIndex proxyBegin = q->mapFromSource(begin); Q_ASSERT(proxyBegin.column() == 0); const QModelIndex proxyEnd = q->mapFromSource(end); Q_EMIT q->dataChanged(proxyBegin, proxyEnd.sibling(proxyEnd.row(), TodoModel::ColumnCount - 1)); } void TodoModel::Private::onHeaderDataChanged(Qt::Orientation orientation, int first, int last) { Q_EMIT q->headerDataChanged(orientation, first, last); } void TodoModel::Private::onRowsAboutToBeInserted(const QModelIndex &parent, int begin, int end) { const QModelIndex index = q->mapFromSource(parent); Q_ASSERT(!(parent.isValid() ^ index.isValid())); // Both must be valid, or both invalid Q_ASSERT(!(index.isValid() && index.model() != q)); q->beginInsertRows(index, begin, end); } void TodoModel::Private::onRowsInserted(const QModelIndex &, int, int) { q->endInsertRows(); } void TodoModel::Private::onRowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end) { const QModelIndex index = q->mapFromSource(parent); Q_ASSERT(!(parent.isValid() ^ index.isValid())); // Both must be valid, or both invalid Q_ASSERT(!(index.isValid() && index.model() != q)); q->beginRemoveRows(index, begin, end); } void TodoModel::Private::onRowsRemoved(const QModelIndex &, int, int) { q->endRemoveRows(); } void TodoModel::Private::onRowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow) { Q_UNUSED(sourceParent); Q_UNUSED(sourceStart); Q_UNUSED(sourceEnd); Q_UNUSED(destinationParent); Q_UNUSED(destinationRow); /* Disabled for now, layoutAboutToBeChanged() is emitted q->beginMoveRows( q->mapFromSource( sourceParent ), sourceStart, sourceEnd, q->mapFromSource( destinationParent ), destinationRow ); */ } void TodoModel::Private::onRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int) { /*q->endMoveRows();*/ } void TodoModel::Private::onModelAboutToBeReset() { q->beginResetModel(); } void TodoModel::Private::onModelReset() { q->endResetModel(); } void TodoModel::Private::onLayoutAboutToBeChanged() { Q_ASSERT(m_persistentIndexes.isEmpty()); Q_ASSERT(m_layoutChangePersistentIndexes.isEmpty()); Q_ASSERT(m_columns.isEmpty()); const QModelIndexList persistentIndexes = q->persistentIndexList(); for (const QPersistentModelIndex &persistentIndex : persistentIndexes) { m_persistentIndexes << persistentIndex; // Stuff we have to update onLayoutChanged Q_ASSERT(persistentIndex.isValid()); QModelIndex index_col0 = q->createIndex(persistentIndex.row(), 0, persistentIndex.internalPointer()); const QPersistentModelIndex srcPersistentIndex = q->mapToSource(index_col0); Q_ASSERT(srcPersistentIndex.isValid()); m_layoutChangePersistentIndexes << srcPersistentIndex; m_columns << persistentIndex.column(); } Q_EMIT q->layoutAboutToBeChanged(); } void TodoModel::Private::onLayoutChanged() { for (int i = 0; i < m_persistentIndexes.size(); ++i) { QModelIndex newIndex_col0 = q->mapFromSource(m_layoutChangePersistentIndexes.at(i)); Q_ASSERT(newIndex_col0.isValid()); const int column = m_columns.at(i); QModelIndex newIndex = column == 0 ? newIndex_col0 : q->createIndex(newIndex_col0.row(), column, newIndex_col0.internalPointer()); q->changePersistentIndex(m_persistentIndexes.at(i), newIndex); } m_layoutChangePersistentIndexes.clear(); m_persistentIndexes.clear(); m_columns.clear(); Q_EMIT q->layoutChanged(); } TodoModel::TodoModel(const EventViews::PrefsPtr &preferences, QObject *parent) : QAbstractProxyModel(parent) , d(new Private(preferences, this)) { setObjectName(QStringLiteral("TodoModel")); } TodoModel::~TodoModel() { delete d; } QVariant TodoModel::data(const QModelIndex &index, int role) const { Q_ASSERT(index.isValid()); if (!index.isValid() || !d->m_calendar) { return QVariant(); } const QModelIndex sourceIndex = mapToSource(index.sibling(index.row(), 0)); if (!sourceIndex.isValid()) { return QVariant(); } Q_ASSERT(sourceIndex.isValid()); const Akonadi::Item item = sourceIndex.data(Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); if (!item.isValid()) { qCWarning(CALENDARVIEW_LOG) << "Invalid index: " << sourceIndex; //Q_ASSERT( false ); return QVariant(); } const KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(item); if (!todo) { qCCritical(CALENDARVIEW_LOG) << "item.hasPayload()" << item.hasPayload(); if (item.hasPayload<KCalendarCore::Incidence::Ptr>()) { KCalendarCore::Incidence::Ptr incidence = item.payload<KCalendarCore::Incidence::Ptr>(); if (incidence) { qCCritical(CALENDARVIEW_LOG) << "It's actually " << incidence->type(); } } Q_ASSERT(!"There's no to-do."); return QVariant(); } if (role == Qt::DisplayRole) { switch (index.column()) { case SummaryColumn: return QVariant(todo->summary()); case RecurColumn: if (todo->recurs()) { if (todo->hasRecurrenceId()) { return i18nc("yes, an exception to a recurring to-do", "Exception"); } else { return i18nc("yes, recurring to-do", "Yes"); } } else { return i18nc("no, not a recurring to-do", "No"); } case PriorityColumn: if (todo->priority() == 0) { return QVariant(QStringLiteral("--")); } return QVariant(todo->priority()); case PercentColumn: return QVariant(todo->percentComplete()); case StartDateColumn: return todo->hasStartDate() ? QLocale().toString( todo->dtStart().toLocalTime().date(), QLocale::ShortFormat) : QVariant(QString()); case DueDateColumn: return todo->hasDueDate() ? QLocale().toString( todo->dtDue().toLocalTime().date(), QLocale::ShortFormat) : QVariant(QString()); case CategoriesColumn: { QString categories = todo->categories().join( i18nc("delimiter for joining category names", ",")); return QVariant(categories); } case DescriptionColumn: return QVariant(todo->description()); case CalendarColumn: return QVariant(CalendarSupport::displayName(d->m_calendar.data(), item.parentCollection())); } return QVariant(); } if (role == Qt::EditRole) { switch (index.column()) { case SummaryColumn: return QVariant(todo->summary()); case RecurColumn: return QVariant(todo->recurs()); case PriorityColumn: return QVariant(todo->priority()); case PercentColumn: return QVariant(todo->percentComplete()); case StartDateColumn: return QVariant(todo->dtStart().date()); case DueDateColumn: return QVariant(todo->dtDue().date()); case CategoriesColumn: return QVariant(todo->categories()); case DescriptionColumn: return QVariant(todo->description()); case CalendarColumn: return QVariant(CalendarSupport::displayName(d->m_calendar.data(), item.parentCollection())); } return QVariant(); } // set the tooltip for every item if (role == Qt::ToolTipRole) { if (d->m_preferences->enableToolTips()) { return QVariant(KCalUtils::IncidenceFormatter::toolTipStr( CalendarSupport::displayName(d->m_calendar.data(), item.parentCollection()), todo, QDate(), true)); } else { return QVariant(); } } // background colour for todos due today or overdue todos if (role == Qt::BackgroundRole) { if (todo->isOverdue()) { return QVariant( QBrush(d->m_preferences->todoOverdueColor())); } else if (isDueToday(todo)) { return QVariant( QBrush(d->m_preferences->todoDueTodayColor())); } } // indicate if a row is checked (=completed) only in the first column if (role == Qt::CheckStateRole && index.column() == 0) { if (hasChildren(index) && !index.parent().isValid()) { return QVariant(); } if (todo->isCompleted()) { return QVariant(Qt::Checked); } else { return QVariant(Qt::Unchecked); } } // icon for recurring todos // It's in the summary column so you don't accidentally click // the checkbox ( which increments the next occurrence date ). if (role == Qt::DecorationRole && index.column() == SummaryColumn) { if (todo->recurs()) { return QVariant(QIcon::fromTheme(QStringLiteral("task-recurring"))); } } // category colour if (role == Qt::DecorationRole && index.column() == SummaryColumn) { QStringList categories = todo->categories(); return categories.isEmpty() ? QVariant() : QVariant(CalendarSupport::KCalPrefs::instance()->categoryColor(categories.first())); } else if (role == Qt::DecorationRole) { return QVariant(); } if (role == TodoRole) { return QVariant::fromValue(item); } if (role == IsRichTextRole) { if (index.column() == SummaryColumn) { return QVariant(todo->summaryIsRich()); } else if (index.column() == DescriptionColumn) { return QVariant(todo->descriptionIsRich()); } else { return QVariant(); } } if (role == Qt::TextAlignmentRole) { switch (index.column()) { // If you change this, change headerData() too. case RecurColumn: case PriorityColumn: case PercentColumn: case StartDateColumn: case DueDateColumn: case CategoriesColumn: case CalendarColumn: return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); } return QVariant(Qt::AlignLeft | Qt::AlignVCenter); } if (sourceModel()) { return sourceModel()->data(mapToSource(index.sibling(index.row(), 0)), role); } return QVariant(); } bool TodoModel::setData(const QModelIndex &index, const QVariant &value, int role) { Q_ASSERT(index.isValid()); if (!d->m_changer) { return false; } const QVariant oldValue = data(index, role); if (oldValue == value) { // Nothing changed, the user used one of the QStyledDelegate's editors but seted the old value // Lets just skip this then and avoid a roundtrip to akonadi, and avoid sending invitations return true; } const Akonadi::Item item = data(index, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); const KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(item); if (!item.isValid() || !todo) { qCWarning(CALENDARVIEW_LOG) << "TodoModel::setData() called, bug item is invalid or doesn't have payload"; Q_ASSERT(false); return false; } if (d->m_calendar->hasRight(item, Akonadi::Collection::CanChangeItem)) { KCalendarCore::Todo::Ptr oldTodo(todo->clone()); if (role == Qt::CheckStateRole && index.column() == 0) { const bool checked = static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked; if (checked) { todo->setCompleted(QDateTime::currentDateTimeUtc()); // Because it calls Todo::recurTodo() } else { todo->setCompleted(false); } } if (role == Qt::EditRole) { switch (index.column()) { case SummaryColumn: if (!value.toString().isEmpty()) { todo->setSummary(value.toString()); } break; case PriorityColumn: todo->setPriority(value.toInt()); break; case PercentColumn: todo->setPercentComplete(value.toInt()); break; case StartDateColumn: { QDateTime tmp = todo->dtStart(); tmp.setDate(value.toDate()); todo->setDtStart(tmp); break; } case DueDateColumn: { QDateTime tmp = todo->dtDue(); tmp.setDate(value.toDate()); todo->setDtDue(tmp); break; } case CategoriesColumn: todo->setCategories(value.toStringList()); break; case DescriptionColumn: todo->setDescription(value.toString()); break; } } if (!todo->dirtyFields().isEmpty()) { d->m_changer->modifyIncidence(item, oldTodo); // modifyIncidence will eventually call the view's // changeIncidenceDisplay method, which in turn // will call processChange. processChange will then emit // dataChanged to the view, so we don't have to // do it here } return true; } else { if (!(role == Qt::CheckStateRole && index.column() == 0)) { //KOHelper::showSaveIncidenceErrorMsg( 0, todo ); //TODO pass parent qCCritical(CALENDARVIEW_LOG) << "Unable to modify incidence"; } return false; } } int TodoModel::rowCount(const QModelIndex &parent) const { if (sourceModel()) { if (parent.isValid()) { QModelIndex parent_col0 = createIndex(parent.row(), 0, parent.internalPointer()); return sourceModel()->rowCount(mapToSource(parent_col0)); } else { return sourceModel()->rowCount(); } } return 0; } int TodoModel::columnCount(const QModelIndex &) const { return ColumnCount; } void TodoModel::setSourceModel(QAbstractItemModel *model) { if (model == sourceModel()) { return; } beginResetModel(); if (sourceModel()) { disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), d, SLOT(onDataChanged(QModelIndex,QModelIndex))); disconnect(sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)), d, SLOT(onHeaderDataChanged(Qt::Orientation,int,int))); disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), d, SLOT(onRowsInserted(QModelIndex,int,int))); disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), d, SLOT(onRowsRemoved(QModelIndex,int,int))); disconnect(sourceModel(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), d, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int))); disconnect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeInserted(QModelIndex,int,int))); disconnect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeRemoved(QModelIndex,int,int))); disconnect(sourceModel(), SIGNAL(modelAboutToBeReset()), d, SLOT(onModelAboutToBeReset())); disconnect(sourceModel(), SIGNAL(modelReset()), d, SLOT(onModelReset())); disconnect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), d, SLOT(onLayoutAboutToBeChanged())); disconnect(sourceModel(), SIGNAL(layoutChanged()), d, SLOT(onLayoutChanged())); } QAbstractProxyModel::setSourceModel(model); if (sourceModel()) { connect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), d, SLOT(onDataChanged(QModelIndex,QModelIndex))); connect(sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)), d, SLOT(onHeaderDataChanged(Qt::Orientation,int,int))); connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeInserted(QModelIndex,int,int))); connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), d, SLOT(onRowsInserted(QModelIndex,int,int))); connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeRemoved(QModelIndex,int,int))); connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), d, SLOT(onRowsRemoved(QModelIndex,int,int))); connect(sourceModel(), SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), d, SLOT(onRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); connect(sourceModel(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), d, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int))); connect(sourceModel(), SIGNAL(modelAboutToBeReset()), d, SLOT(onModelAboutToBeReset())); connect(sourceModel(), SIGNAL(modelReset()), d, SLOT(onModelReset())); connect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), d, SLOT(onLayoutAboutToBeChanged())); connect(sourceModel(), SIGNAL(layoutChanged()), d, SLOT(onLayoutChanged())); } endResetModel(); } void TodoModel::setIncidenceChanger(Akonadi::IncidenceChanger *changer) { d->m_changer = changer; } QVariant TodoModel::headerData(int column, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal) { return QVariant(); } if (role == Qt::DisplayRole) { switch (column) { case SummaryColumn: return QVariant(i18n("Summary")); case RecurColumn: return QVariant(i18n("Recurs")); case PriorityColumn: return QVariant(i18n("Priority")); case PercentColumn: return QVariant(i18nc("@title:column percent complete", "Complete")); case StartDateColumn: return QVariant(i18n("Start Date")); case DueDateColumn: return QVariant(i18n("Due Date")); case CategoriesColumn: return QVariant(i18n("Categories")); case DescriptionColumn: return QVariant(i18n("Description")); case CalendarColumn: return QVariant(i18n("Calendar")); } } if (role == Qt::TextAlignmentRole) { switch (column) { // If you change this, change data() too. case RecurColumn: case PriorityColumn: case PercentColumn: case StartDateColumn: case DueDateColumn: case CategoriesColumn: case CalendarColumn: return QVariant(Qt::AlignHCenter); } return QVariant(); } return QVariant(); } void TodoModel::setCalendar(const Akonadi::ETMCalendar::Ptr &calendar) { d->m_calendar = calendar; } Qt::DropActions TodoModel::supportedDropActions() const { // Qt::CopyAction not supported yet return Qt::MoveAction; } QStringList TodoModel::mimeTypes() const { static QStringList list; if (list.isEmpty()) { list << KCalUtils::ICalDrag::mimeType() << KCalUtils::VCalDrag::mimeType(); } return list; } QMimeData *TodoModel::mimeData(const QModelIndexList &indexes) const { Akonadi::Item::List items; for (const QModelIndex &index : indexes) { const Akonadi::Item item = this->data(index, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); if (item.isValid() && !items.contains(item)) { items.push_back(item); } } return CalendarSupport::createMimeData(items); } bool TodoModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); if (action != Qt::MoveAction) { qCWarning(CALENDARVIEW_LOG) << "No action other than MoveAction currently supported!"; //TODO return false; } if (d->m_calendar && d->m_changer && (KCalUtils::ICalDrag::canDecode(data) || KCalUtils::VCalDrag::canDecode(data))) { KCalUtils::DndFactory dndFactory(d->m_calendar); KCalendarCore::Todo::Ptr t = dndFactory.createDropTodo(data); KCalendarCore::Event::Ptr e = dndFactory.createDropEvent(data); if (t) { // we don't want to change the created todo, but the one which is already // stored in our calendar / tree const Akonadi::Item item = d->findItemByUid(t->uid(), QModelIndex()); KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(item); KCalendarCore::Todo::Ptr destTodo; if (parent.isValid()) { const Akonadi::Item parentItem = this->data(parent, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); if (parentItem.isValid()) { destTodo = CalendarSupport::todo(parentItem); } } KCalendarCore::Incidence::Ptr tmp = destTodo; while (tmp) { if (tmp->uid() == todo->uid()) { //correct, don't use instanceIdentifier() here KMessageBox::information( nullptr, i18n("Cannot move to-do to itself or a child of itself."), i18n("Drop To-do"), QStringLiteral("NoDropTodoOntoItself")); return false; } const QString parentUid = tmp->relatedTo(); tmp = CalendarSupport::incidence(d->m_calendar->item(parentUid)); } if (!destTodo || !destTodo->hasRecurrenceId()) { KCalendarCore::Todo::Ptr oldTodo = KCalendarCore::Todo::Ptr(todo->clone()); // destTodo is empty when we drag a to-do out of a relationship todo->setRelatedTo(destTodo ? destTodo->uid() : QString()); d->m_changer->modifyIncidence(item, oldTodo); // again, no need to Q_EMIT dataChanged, that's done by processChange return true; } else { qCDebug(CALENDARVIEW_LOG) << "Todo's with recurring id can't have child todos yet."; return false; } } else if (e) { // TODO: Implement dropping an event onto a to-do: Generate a relationship to the event! } else { if (!parent.isValid()) { // TODO we should create a new todo with the data in the drop object qCDebug(CALENDARVIEW_LOG) << "TODO: Create a new todo with the given data"; return false; } const Akonadi::Item parentItem = this->data(parent, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); KCalendarCore::Todo::Ptr destTodo = CalendarSupport::todo(parentItem); if (data->hasText()) { QString text = data->text(); KCalendarCore::Todo::Ptr oldTodo = KCalendarCore::Todo::Ptr(destTodo->clone()); if (text.startsWith(QLatin1String("file:"))) { destTodo->addAttachment(KCalendarCore::Attachment(text)); } else { QStringList emails = KEmailAddress::splitAddressList(text); for (QStringList::ConstIterator it = emails.constBegin(); it != emails.constEnd(); ++it) { QString name, email, comment; if (KEmailAddress::splitAddress(*it, name, email, comment) == KEmailAddress::AddressOk) { destTodo->addAttendee(KCalendarCore::Attendee(name, email)); } } } d->m_changer->modifyIncidence(parentItem, oldTodo); return true; } } } return false; } Qt::ItemFlags TodoModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return Qt::NoItemFlags; } Qt::ItemFlags ret = QAbstractItemModel::flags(index); const Akonadi::Item item = data(index, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); if (!item.isValid()) { Q_ASSERT(mapToSource(index).isValid()); qCWarning(CALENDARVIEW_LOG) << "Item is invalid " << index; Q_ASSERT(false); return Qt::NoItemFlags; } ret |= Qt::ItemIsDragEnabled; const KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(item); if (d->m_calendar->hasRight(item, Akonadi::Collection::CanChangeItem)) { // the following columns are editable: switch (index.column()) { case SummaryColumn: case PriorityColumn: case PercentColumn: case StartDateColumn: case DueDateColumn: case CategoriesColumn: ret |= Qt::ItemIsEditable; break; case DescriptionColumn: if (!todo->descriptionIsRich()) { ret |= Qt::ItemIsEditable; } break; } } if (index.column() == 0) { // whole rows should have checkboxes, so append the flag for the // first item of every row only. Also, only the first item of every // row should be used as a target for a drag and drop operation. ret |= Qt::ItemIsUserCheckable |Qt::ItemIsDropEnabled; } return ret; } QModelIndex TodoModel::mapFromSource(const QModelIndex &sourceIndex) const { if (!sourceModel() || !sourceIndex.isValid()) { return QModelIndex(); } Q_ASSERT(sourceIndex.internalPointer()); return createIndex(sourceIndex.row(), 0, sourceIndex.internalPointer()); } QModelIndex TodoModel::mapToSource(const QModelIndex &proxyIndex) const { if (!sourceModel() || !proxyIndex.isValid()) { return QModelIndex(); } if (proxyIndex.column() != 0) { qCCritical(CALENDARVIEW_LOG) << "Map to source called with column>0, but source model only has 1 column"; Q_ASSERT(false); } Q_ASSERT(proxyIndex.internalPointer()); // we convert to column 0 const QModelIndex sourceIndex = SourceModelIndex(proxyIndex.row(), 0, proxyIndex.internalPointer(), sourceModel()); return sourceIndex; } QModelIndex TodoModel::index(int row, int column, const QModelIndex &parent) const { if (!sourceModel()) { return QModelIndex(); } Q_ASSERT(!parent.isValid() || parent.internalPointer()); QModelIndex parent_col0 = parent.isValid() ? createIndex(parent.row(), 0, parent.internalPointer()) : QModelIndex(); // Lets preserve the original internalPointer const QModelIndex index = mapFromSource(sourceModel()->index(row, 0, mapToSource(parent_col0))); Q_ASSERT(!index.isValid() || index.internalPointer()); if (index.isValid()) { return createIndex(row, column, index.internalPointer()); } return QModelIndex(); } QModelIndex TodoModel::parent(const QModelIndex &child) const { if (!sourceModel() || !child.isValid()) { return QModelIndex(); } Q_ASSERT(child.internalPointer()); const QModelIndex child_col0 = createIndex(child.row(), 0, child.internalPointer()); QModelIndex parentIndex = mapFromSource(sourceModel()->parent(mapToSource(child_col0))); Q_ASSERT(!parentIndex.isValid() || parentIndex.internalPointer()); if (parentIndex.isValid()) { // preserve original column return createIndex(parentIndex.row(), child.column(), parentIndex.internalPointer()); } return QModelIndex(); } QModelIndex TodoModel::buddy(const QModelIndex &index) const { // We reimplement because the default implementation calls mapToSource() and // source model doesn't have the same number of columns. return index; } 0707010000004E000081A40000000200000002000000015F0BF3C900000EE3000000000000000000000000000000000000004300000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/todomodel.h/* Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> Copyright (c) 2012 Sérgio Martins <iamsergio@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef CALENDARVIEWS_TODOMODEL_H #define CALENDARVIEWS_TODOMODEL_H #include "prefs.h" #include <Akonadi/Calendar/IncidenceChanger> #include <Akonadi/Calendar/ETMCalendar> #include <Item> #include <KCalendarCore/Todo> #include <QAbstractItemModel> #include <EntityTreeModel> #include <QAbstractProxyModel> class QMimeData; class TodoModel : public QAbstractProxyModel { Q_OBJECT public: /** This enum defines all columns this model provides */ enum { SummaryColumn = 0, RecurColumn, PriorityColumn, PercentColumn, StartDateColumn, DueDateColumn, CategoriesColumn, DescriptionColumn, CalendarColumn, ColumnCount // Just for iteration/column count purposes. Always keep at the end of enum. }; /** This enum defines the user defined roles of the items in this model */ enum { TodoRole = Akonadi::EntityTreeModel::UserRole + 1, IsRichTextRole }; explicit TodoModel(const EventViews::PrefsPtr &preferences, QObject *parent = nullptr); ~TodoModel() override; Q_REQUIRED_RESULT int rowCount(const QModelIndex &parent = QModelIndex()) const override; Q_REQUIRED_RESULT int columnCount(const QModelIndex &parent = QModelIndex()) const override; void setSourceModel(QAbstractItemModel *sourceModel) override; Q_REQUIRED_RESULT QVariant data(const QModelIndex &index, int role) const override; Q_REQUIRED_RESULT bool setData(const QModelIndex &index, const QVariant &value, int role) override; Q_REQUIRED_RESULT QVariant headerData(int section, Qt::Orientation, int role) const override; void setCalendar(const Akonadi::ETMCalendar::Ptr &calendar); void setIncidenceChanger(Akonadi::IncidenceChanger *changer); Q_REQUIRED_RESULT QMimeData *mimeData(const QModelIndexList &indexes) const override; Q_REQUIRED_RESULT bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; Q_REQUIRED_RESULT QStringList mimeTypes() const override; Q_REQUIRED_RESULT Qt::DropActions supportedDropActions() const override; Q_REQUIRED_RESULT Qt::ItemFlags flags(const QModelIndex &index) const override; Q_REQUIRED_RESULT QModelIndex parent(const QModelIndex &child) const override; Q_REQUIRED_RESULT QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override; Q_REQUIRED_RESULT QModelIndex mapToSource(const QModelIndex &proxyIndex) const override; Q_REQUIRED_RESULT QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; Q_REQUIRED_RESULT QModelIndex buddy(const QModelIndex &index) const override; private: class Private; Private *const d; }; #endif 0707010000004F000081A40000000200000002000000015F0BF3C900000AC6000000000000000000000000000000000000004500000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/todomodel_p.h/* Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> Copyright (c) 2012 Sérgio Martins <iamsergio@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef CALENDARVIEWS_TODOMODEL_P_H #define CALENDARVIEWS_TODOMODEL_P_H #include "todomodel.h" #include <Item> #include <Akonadi/Calendar/ETMCalendar> #include <QModelIndex> #include <QString> namespace Akonadi { class IncidenceChanger; } class TodoModel::Private : public QObject { Q_OBJECT public: Private(const EventViews::PrefsPtr &preferences, TodoModel *qq); //TODO: O(N) complexity, see if the profiler complains about this Akonadi::Item findItemByUid(const QString &uid, const QModelIndex &parent) const; public: Akonadi::ETMCalendar::Ptr m_calendar; Akonadi::IncidenceChanger *m_changer = nullptr; //For adjusting persistent indexes QList<QPersistentModelIndex> m_layoutChangePersistentIndexes; QModelIndexList m_persistentIndexes; QList<int> m_columns; EventViews::PrefsPtr m_preferences; private Q_SLOTS: void onDataChanged(const QModelIndex &begin, const QModelIndex &end); void onHeaderDataChanged(Qt::Orientation orientation, int first, int last); void onRowsAboutToBeInserted(const QModelIndex &parent, int begin, int end); void onRowsInserted(const QModelIndex &parent, int begin, int end); void onRowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end); void onRowsRemoved(const QModelIndex &parent, int begin, int end); void onRowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow); void onRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int); void onModelAboutToBeReset(); void onModelReset(); void onLayoutAboutToBeChanged(); void onLayoutChanged(); private: TodoModel *const q; }; #endif 07070100000050000081A40000000200000002000000015F0BF3C90000B06C000000000000000000000000000000000000004400000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/todoview.cpp/* This file is part of KOrganizer. Copyright (c) 2000,2001,2003 Cornelius Schumacher <schumacher@kde.org> Copyright (c) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (c) 2005 Rafal Rzepecki <divide@users.sourceforge.net> Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> Copyright (c) 2013 Sérgio Martins <iamsergio@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "todoview.h" #include "incidencetreemodel.h" #include "tododelegates.h" #include "todomodel.h" #include "todoviewquickaddline.h" #include "todoviewquicksearch.h" #include "todoviewsortfilterproxymodel.h" #include "todoviewview.h" #include "calendarview_debug.h" #include <AkonadiCore/EntityMimeTypeFilterModel> #include <AkonadiCore/TagFetchJob> #include <AkonadiWidgets/ETMViewStateSaver> #include <CalendarSupport/KCalPrefs> #include <CalendarSupport/Utils> #include <CalendarSupport/KDatePickerPopup> #include <KCalendarCore/CalFormat> #include <KJob> #include <QGridLayout> #include <QHeaderView> #include <QIcon> #include <QMenu> #include <QToolButton> Q_DECLARE_METATYPE(QPointer<QMenu>) using namespace EventViews; using namespace KCalendarCore; namespace EventViews { // We share this struct between all views, for performance and memory purposes class ModelStack { public: ModelStack(const EventViews::PrefsPtr &preferences, QObject *parent_) : todoModel(new TodoModel(preferences)) , parent(parent_) , calendar(nullptr) , todoTreeModel(nullptr) , todoFlatModel(nullptr) , prefs(preferences) { } ~ModelStack() { delete todoModel; delete todoTreeModel; delete todoFlatModel; } void registerView(TodoView *view) { views << view; } void unregisterView(TodoView *view) { views.removeAll(view); } void setFlatView(bool flat) { const QString todoMimeType = QStringLiteral("application/x-vnd.akonadi.calendar.todo"); if (flat) { for (TodoView *view : qAsConst(views)) { // In flatview dropping confuses users and it's very easy to drop into a child item view->mView->setDragDropMode(QAbstractItemView::DragOnly); view->setFlatView(flat, /**propagate=*/ false); // So other views update their toggle icon if (todoTreeModel) { view->saveViewState(); // Save the tree state before it's gone } } delete todoFlatModel; todoFlatModel = new Akonadi::EntityMimeTypeFilterModel(parent); todoFlatModel->addMimeTypeInclusionFilter(todoMimeType); todoFlatModel->setSourceModel(calendar ? calendar->model() : nullptr); todoModel->setSourceModel(todoFlatModel); delete todoTreeModel; todoTreeModel = nullptr; } else { delete todoTreeModel; todoTreeModel = new IncidenceTreeModel(QStringList() << todoMimeType, parent); for (TodoView *view : qAsConst(views)) { QObject::connect(todoTreeModel, &IncidenceTreeModel::indexChangedParent, view, &TodoView::expandIndex); QObject::connect(todoTreeModel, &IncidenceTreeModel::batchInsertionFinished, view, &TodoView::restoreViewState); view->mView->setDragDropMode(QAbstractItemView::DragDrop); view->setFlatView(flat, /**propagate=*/ false); // So other views update their toggle icon } todoTreeModel->setSourceModel(calendar ? calendar->model() : nullptr); todoModel->setSourceModel(todoTreeModel); delete todoFlatModel; todoFlatModel = nullptr; } for (TodoView *view : qAsConst(views)) { view->mFlatViewButton->blockSignals(true); // We block signals to avoid recursion, we have two TodoViews and mFlatViewButton is synchronized view->mFlatViewButton->setChecked(flat); view->mFlatViewButton->blockSignals(false); view->mView->setRootIsDecorated(!flat); view->restoreViewState(); } prefs->setFlatListTodo(flat); prefs->writeConfig(); } void setCalendar(const Akonadi::ETMCalendar::Ptr &newCalendar) { calendar = newCalendar; todoModel->setCalendar(calendar); if (todoTreeModel) { todoTreeModel->setSourceModel(calendar ? calendar->model() : nullptr); } } bool isFlatView() const { return todoFlatModel != nullptr; } TodoModel *todoModel = nullptr; QList<TodoView *> views; QObject *parent = nullptr; Akonadi::ETMCalendar::Ptr calendar; IncidenceTreeModel *todoTreeModel = nullptr; Akonadi::EntityMimeTypeFilterModel *todoFlatModel = nullptr; EventViews::PrefsPtr prefs; }; } // Don't use K_GLOBAL_STATIC, see QTBUG-22667 static ModelStack *sModels = nullptr; TodoView::TodoView(const EventViews::PrefsPtr &prefs, bool sidebarView, QWidget *parent) : EventView(parent) , mQuickSearch(nullptr) , mQuickAdd(nullptr) , mTreeStateRestorer(nullptr) , mSidebarView(sidebarView) , mResizeColumnsScheduled(false) { mResizeColumnsTimer = new QTimer(this); connect(mResizeColumnsTimer, &QTimer::timeout, this, &TodoView::resizeColumns); mResizeColumnsTimer->setInterval(100); // so we don't overdue it when user resizes window manually mResizeColumnsTimer->setSingleShot(true); setPreferences(prefs); if (!sModels) { sModels = new ModelStack(prefs, parent); } sModels->registerView(this); mProxyModel = new TodoViewSortFilterProxyModel(preferences(), this); mProxyModel->setSourceModel(sModels->todoModel); mProxyModel->setDynamicSortFilter(true); mProxyModel->setFilterKeyColumn(TodoModel::SummaryColumn); mProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); mProxyModel->setSortRole(Qt::EditRole); connect(mProxyModel, &TodoViewSortFilterProxyModel::rowsInserted, this, &TodoView::onRowsInserted); if (!mSidebarView) { mQuickSearch = new TodoViewQuickSearch(calendar(), this); mQuickSearch->setVisible(prefs->enableTodoQuickSearch()); connect(mQuickSearch, &TodoViewQuickSearch::searchTextChanged, mProxyModel, QOverload<const QString&>::of(&QSortFilterProxyModel::setFilterRegExp)); connect(mQuickSearch, &TodoViewQuickSearch::searchTextChanged, this, &TodoView::restoreViewState); connect(mQuickSearch, &TodoViewQuickSearch::filterCategoryChanged, mProxyModel, &TodoViewSortFilterProxyModel::setCategoryFilter); connect(mQuickSearch, &TodoViewQuickSearch::filterCategoryChanged, this, &TodoView::restoreViewState); connect(mQuickSearch, &TodoViewQuickSearch::filterPriorityChanged, mProxyModel, &TodoViewSortFilterProxyModel::setPriorityFilter); connect(mQuickSearch, &TodoViewQuickSearch::filterPriorityChanged, this, &TodoView::restoreViewState); } mView = new TodoViewView(this); mView->setModel(mProxyModel); mView->setContextMenuPolicy(Qt::CustomContextMenu); mView->setSortingEnabled(true); mView->setAutoExpandDelay(250); mView->setDragDropMode(QAbstractItemView::DragDrop); mView->setExpandsOnDoubleClick(false); mView->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed); connect(mView->header(), &QHeaderView::geometriesChanged, this, &TodoView::scheduleResizeColumns); connect(mView, &TodoViewView::visibleColumnCountChanged, this, &TodoView::resizeColumns); TodoRichTextDelegate *richTextDelegate = new TodoRichTextDelegate(mView); mView->setItemDelegateForColumn(TodoModel::SummaryColumn, richTextDelegate); mView->setItemDelegateForColumn(TodoModel::DescriptionColumn, richTextDelegate); TodoPriorityDelegate *priorityDelegate = new TodoPriorityDelegate(mView); mView->setItemDelegateForColumn(TodoModel::PriorityColumn, priorityDelegate); TodoDueDateDelegate *startDateDelegate = new TodoDueDateDelegate(mView); mView->setItemDelegateForColumn(TodoModel::StartDateColumn, startDateDelegate); TodoDueDateDelegate *dueDateDelegate = new TodoDueDateDelegate(mView); mView->setItemDelegateForColumn(TodoModel::DueDateColumn, dueDateDelegate); TodoCompleteDelegate *completeDelegate = new TodoCompleteDelegate(mView); mView->setItemDelegateForColumn(TodoModel::PercentColumn, completeDelegate); mCategoriesDelegate = new TodoCategoriesDelegate(mView); mView->setItemDelegateForColumn(TodoModel::CategoriesColumn, mCategoriesDelegate); connect(mView, &TodoViewView::customContextMenuRequested, this, &TodoView::contextMenu); connect(mView, &TodoViewView::doubleClicked, this, &TodoView::itemDoubleClicked); connect( mView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &TodoView::selectionChanged); mQuickAdd = new TodoViewQuickAddLine(this); mQuickAdd->setClearButtonEnabled(true); mQuickAdd->setVisible(preferences()->enableQuickTodo()); connect(mQuickAdd, SIGNAL(returnPressed(Qt::KeyboardModifiers)), this, SLOT(addQuickTodo(Qt::KeyboardModifiers))); mFullViewButton = nullptr; if (!mSidebarView) { mFullViewButton = new QToolButton(this); mFullViewButton->setAutoRaise(true); mFullViewButton->setCheckable(true); mFullViewButton->setToolTip( i18nc("@info:tooltip", "Display to-do list in a full window")); mFullViewButton->setWhatsThis( i18nc("@info:whatsthis", "Checking this option will cause the to-do view to use the full window.")); } mFlatViewButton = new QToolButton(this); mFlatViewButton->setAutoRaise(true); mFlatViewButton->setCheckable(true); mFlatViewButton->setToolTip( i18nc("@info:tooltip", "Display to-dos in flat list instead of a tree")); mFlatViewButton->setWhatsThis( i18nc("@info:whatsthis", "Checking this option will cause the to-dos to be displayed as a " "flat list instead of a hierarchical tree; the parental " "relationships are removed in the display.")); connect(mFlatViewButton, SIGNAL(toggled(bool)), SLOT(setFlatView(bool))); if (mFullViewButton) { connect(mFullViewButton, &QToolButton::toggled, this, &TodoView::setFullView); } QGridLayout *layout = new QGridLayout(this); layout->setContentsMargins(0, 0, 0, 0); if (!mSidebarView) { layout->addWidget(mQuickSearch, 0, 0, 1, 2); } layout->addWidget(mView, 1, 0, 1, 2); layout->setRowStretch(1, 1); layout->addWidget(mQuickAdd, 2, 0); // Dummy layout just to add a few px of right margin so the checkbox is aligned // with the QAbstractItemView's viewport. QHBoxLayout *dummyLayout = new QHBoxLayout(); dummyLayout->setContentsMargins(0, 0, mView->frameWidth() /*right*/, 0); if (!mSidebarView) { QFrame *f = new QFrame(this); f->setFrameShape(QFrame::VLine); f->setFrameShadow(QFrame::Sunken); dummyLayout->addWidget(f); dummyLayout->addWidget(mFullViewButton); } dummyLayout->addWidget(mFlatViewButton); layout->addLayout(dummyLayout, 2, 1); // ---------------- POPUP-MENUS ----------------------- mItemPopupMenu = new QMenu(this); mItemPopupMenuItemOnlyEntries << mItemPopupMenu->addAction( i18nc("@action:inmenu show the to-do", "&Show"), this, &TodoView::showTodo); QAction *a = mItemPopupMenu->addAction( i18nc("@action:inmenu edit the to-do", "&Edit..."), this, &TodoView::editTodo); mItemPopupMenuReadWriteEntries << a; mItemPopupMenuItemOnlyEntries << a; mItemPopupMenu->addSeparator(); mItemPopupMenuItemOnlyEntries << mItemPopupMenu->addAction( QIcon::fromTheme(QStringLiteral("document-print")), i18nc("@action:inmenu print the to-do", "&Print..."), this, &TodoView::printTodo); mItemPopupMenuItemOnlyEntries << mItemPopupMenu->addAction( QIcon::fromTheme(QStringLiteral("document-print-preview")), i18nc("@action:inmenu print preview the to-do", "Print Previe&w..."), this, &TodoView::printPreviewTodo); mItemPopupMenu->addSeparator(); a = mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action:inmenu delete the to-do", "&Delete"), this, &TodoView::deleteTodo); mItemPopupMenuReadWriteEntries << a; mItemPopupMenuItemOnlyEntries << a; mItemPopupMenu->addSeparator(); mItemPopupMenu->addAction( QIcon::fromTheme(QStringLiteral("view-calendar-tasks")), i18nc("@action:inmenu create a new to-do", "New &To-do..."), this, &TodoView::newTodo); a = mItemPopupMenu->addAction( i18nc("@action:inmenu create a new sub-to-do", "New Su&b-to-do..."), this, &TodoView::newSubTodo); mItemPopupMenuReadWriteEntries << a; mItemPopupMenuItemOnlyEntries << a; mMakeTodoIndependent = mItemPopupMenu->addAction( i18nc("@action:inmenu", "&Make this To-do Independent"), this, &TodoView::unSubTodoSignal); mMakeSubtodosIndependent = mItemPopupMenu->addAction( i18nc("@action:inmenu", "Make all Sub-to-dos &Independent"), this, &TodoView::unAllSubTodoSignal); mItemPopupMenuItemOnlyEntries << mMakeTodoIndependent; mItemPopupMenuItemOnlyEntries << mMakeSubtodosIndependent; mItemPopupMenuReadWriteEntries << mMakeTodoIndependent; mItemPopupMenuReadWriteEntries << mMakeSubtodosIndependent; mItemPopupMenu->addSeparator(); a = mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("appointment-new")), i18nc("@action:inmenu", "Create Event"), this, SLOT(createEvent())); a->setObjectName(QStringLiteral("createevent")); mItemPopupMenuReadWriteEntries << a; mItemPopupMenuItemOnlyEntries << a; a = mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("view-pim-notes")), i18nc("@action:inmenu", "Create Note"), this, SLOT(createNote())); a->setObjectName(QStringLiteral("createnote")); mItemPopupMenuReadWriteEntries << a; mItemPopupMenuItemOnlyEntries << a; mItemPopupMenu->addSeparator(); mCopyPopupMenu = new CalendarSupport::KDatePickerPopup(CalendarSupport::KDatePickerPopup::NoDate |CalendarSupport::KDatePickerPopup::DatePicker |CalendarSupport::KDatePickerPopup::Words, QDate::currentDate(), this); mCopyPopupMenu->setTitle(i18nc("@title:menu", "&Copy To")); connect(mCopyPopupMenu, &CalendarSupport::KDatePickerPopup::dateChanged, this, &TodoView::copyTodoToDate); connect(mCopyPopupMenu, &CalendarSupport::KDatePickerPopup::dateChanged, mItemPopupMenu, &QMenu::hide); mMovePopupMenu = new CalendarSupport:: KDatePickerPopup(CalendarSupport::KDatePickerPopup::NoDate |CalendarSupport::KDatePickerPopup::DatePicker |CalendarSupport::KDatePickerPopup::Words, QDate::currentDate(), this); mMovePopupMenu->setTitle(i18nc("@title:menu", "&Move To")); connect(mMovePopupMenu, &CalendarSupport::KDatePickerPopup::dateChanged, this, &TodoView::setNewDate); connect(mMovePopupMenu, &CalendarSupport::KDatePickerPopup::dateChanged, mItemPopupMenu, &QMenu::hide); mItemPopupMenu->insertMenu(nullptr, mCopyPopupMenu); mItemPopupMenu->insertMenu(nullptr, mMovePopupMenu); mItemPopupMenu->addSeparator(); mItemPopupMenu->addAction( i18nc("@action:inmenu delete completed to-dos", "Pur&ge Completed"), this, &TodoView::purgeCompletedSignal); mPriorityPopupMenu = new QMenu(this); mPriority[ mPriorityPopupMenu->addAction( i18nc("@action:inmenu unspecified priority", "unspecified")) ] = 0; mPriority[ mPriorityPopupMenu->addAction( i18nc("@action:inmenu highest priority", "1 (highest)")) ] = 1; mPriority[ mPriorityPopupMenu->addAction( i18nc("@action:inmenu priority value=2", "2")) ] = 2; mPriority[ mPriorityPopupMenu->addAction( i18nc("@action:inmenu priority value=3", "3")) ] = 3; mPriority[ mPriorityPopupMenu->addAction( i18nc("@action:inmenu priority value=4", "4")) ] = 4; mPriority[ mPriorityPopupMenu->addAction( i18nc("@action:inmenu medium priority", "5 (medium)")) ] = 5; mPriority[ mPriorityPopupMenu->addAction( i18nc("@action:inmenu priority value=6", "6")) ] = 6; mPriority[ mPriorityPopupMenu->addAction( i18nc("@action:inmenu priority value=7", "7")) ] = 7; mPriority[ mPriorityPopupMenu->addAction( i18nc("@action:inmenu priority value=8", "8")) ] = 8; mPriority[ mPriorityPopupMenu->addAction( i18nc("@action:inmenu lowest priority", "9 (lowest)")) ] = 9; connect(mPriorityPopupMenu, &QMenu::triggered, this, &TodoView::setNewPriority); mPercentageCompletedPopupMenu = new QMenu(this); for (int i = 0; i <= 100; i += 10) { const QString label = QStringLiteral("%1 %").arg(i); mPercentage[mPercentageCompletedPopupMenu->addAction(label)] = i; } connect(mPercentageCompletedPopupMenu, &QMenu::triggered, this, &TodoView::setNewPercentage); setMinimumHeight(50); // Initialize our proxy models setFlatView(preferences()->flatListTodo()); setFullView(preferences()->fullViewTodo()); updateConfig(); } TodoView::~TodoView() { saveViewState(); sModels->unregisterView(this); if (sModels->views.isEmpty()) { delete sModels; sModels = nullptr; } } void TodoView::expandIndex(const QModelIndex &index) { QModelIndex todoModelIndex = sModels->todoModel->mapFromSource(index); Q_ASSERT(todoModelIndex.isValid()); QModelIndex realIndex = mProxyModel->mapFromSource(todoModelIndex); Q_ASSERT(realIndex.isValid()); while (realIndex.isValid()) { mView->expand(realIndex); realIndex = mProxyModel->parent(realIndex); } } void TodoView::setCalendar(const Akonadi::ETMCalendar::Ptr &calendar) { EventView::setCalendar(calendar); if (!mSidebarView) { mQuickSearch->setCalendar(calendar); } mCategoriesDelegate->setCalendar(calendar); sModels->setCalendar(calendar); restoreViewState(); } Akonadi::Item::List TodoView::selectedIncidences() const { Akonadi::Item::List ret; const QModelIndexList selection = mView->selectionModel()->selectedRows(); ret.reserve(selection.count()); for (const QModelIndex &mi : selection) { ret << mi.data(TodoModel::TodoRole).value<Akonadi::Item>(); } return ret; } DateList TodoView::selectedIncidenceDates() const { // The todo view only lists todo's. It's probably not a good idea to // return something about the selected todo here, because it has got // a couple of dates (creation, due date, completion date), and the // caller could not figure out what he gets. So just return an empty list. return DateList(); } void TodoView::saveLayout(KConfig *config, const QString &group) const { KConfigGroup cfgGroup = config->group(group); QHeaderView *header = mView->header(); QVariantList columnVisibility; QVariantList columnOrder; QVariantList columnWidths; const int headerCount = header->count(); columnVisibility.reserve(headerCount); columnWidths.reserve(headerCount); columnOrder.reserve(headerCount); for (int i = 0; i < headerCount; ++i) { columnVisibility << QVariant(!mView->isColumnHidden(i)); columnWidths << QVariant(header->sectionSize(i)); columnOrder << QVariant(header->visualIndex(i)); } cfgGroup.writeEntry("ColumnVisibility", columnVisibility); cfgGroup.writeEntry("ColumnOrder", columnOrder); cfgGroup.writeEntry("ColumnWidths", columnWidths); cfgGroup.writeEntry("SortAscending", (int)header->sortIndicatorOrder()); if (header->isSortIndicatorShown()) { cfgGroup.writeEntry("SortColumn", header->sortIndicatorSection()); } else { cfgGroup.writeEntry("SortColumn", -1); } if (!mSidebarView) { preferences()->setFullViewTodo(mFullViewButton->isChecked()); } preferences()->setFlatListTodo(mFlatViewButton->isChecked()); } void TodoView::restoreLayout(KConfig *config, const QString &group, bool minimalDefaults) { KConfigGroup cfgGroup = config->group(group); QHeaderView *header = mView->header(); QVariantList columnVisibility = cfgGroup.readEntry("ColumnVisibility", QVariantList()); QVariantList columnOrder = cfgGroup.readEntry("ColumnOrder", QVariantList()); QVariantList columnWidths = cfgGroup.readEntry("ColumnWidths", QVariantList()); if (columnVisibility.isEmpty()) { // if config is empty then use default settings mView->hideColumn(TodoModel::RecurColumn); mView->hideColumn(TodoModel::DescriptionColumn); mView->hideColumn(TodoModel::CalendarColumn); if (minimalDefaults) { mView->hideColumn(TodoModel::PriorityColumn); mView->hideColumn(TodoModel::PercentColumn); mView->hideColumn(TodoModel::DescriptionColumn); mView->hideColumn(TodoModel::CategoriesColumn); } // We don't have any incidences (content) yet, so we delay resizing QTimer::singleShot(0, this, &TodoView::resizeColumns); } else { for (int i = 0; i < header->count() && i < columnOrder.size() && i < columnWidths.size() && i < columnVisibility.size(); i++) { bool visible = columnVisibility[i].toBool(); int width = columnWidths[i].toInt(); int order = columnOrder[i].toInt(); header->resizeSection(i, width); header->moveSection(header->visualIndex(i), order); if (i != 0 && !visible) { mView->hideColumn(i); } } } int sortOrder = cfgGroup.readEntry("SortAscending", (int)Qt::AscendingOrder); int sortColumn = cfgGroup.readEntry("SortColumn", -1); if (sortColumn >= 0) { mView->sortByColumn(sortColumn, (Qt::SortOrder)sortOrder); } mFlatViewButton->setChecked(cfgGroup.readEntry("FlatView", false)); } void TodoView::setIncidenceChanger(Akonadi::IncidenceChanger *changer) { EventView::setIncidenceChanger(changer); sModels->todoModel->setIncidenceChanger(changer); } void TodoView::showDates(const QDate &start, const QDate &end, const QDate &) { // There is nothing to do here for the Todo View Q_UNUSED(start); Q_UNUSED(end); } void TodoView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) { Q_UNUSED(incidenceList); Q_UNUSED(date); } void TodoView::updateView() { // View is always updated, it's connected to ETM. } void TodoView::changeIncidenceDisplay(const Akonadi::Item &, Akonadi::IncidenceChanger::ChangeType) { // Don't do anything, model is connected to ETM, it's up to date } void TodoView::updateConfig() { Q_ASSERT(preferences()); if (!mSidebarView && mQuickSearch) { mQuickSearch->setVisible(preferences()->enableTodoQuickSearch()); } if (mQuickAdd) { mQuickAdd->setVisible(preferences()->enableQuickTodo()); } updateView(); } void TodoView::clearSelection() { mView->selectionModel()->clearSelection(); } void TodoView::addTodo(const QString &summary, const Akonadi::Item &parentItem, const QStringList &categories) { const QString summaryTrimmed = summary.trimmed(); if (!changer() || summaryTrimmed.isEmpty()) { return; } KCalendarCore::Todo::Ptr parent = CalendarSupport::todo(parentItem); KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo); todo->setSummary(summaryTrimmed); todo->setOrganizer(Person(CalendarSupport::KCalPrefs::instance()->fullName(), CalendarSupport::KCalPrefs::instance()->email())); todo->setCategories(categories); if (parent && !parent->hasRecurrenceId()) { todo->setRelatedTo(parent->uid()); } Akonadi::Collection collection; // Use the same collection of the parent. if (parentItem.isValid()) { // Don't use parentColection() since it might be a virtual collection collection = calendar()->collection(parentItem.storageCollectionId()); } changer()->createIncidence(todo, collection, this); } void TodoView::addQuickTodo(Qt::KeyboardModifiers modifiers) { if (modifiers == Qt::NoModifier) { /*const QModelIndex index = */ addTodo(mQuickAdd->text(), Akonadi::Item(), mProxyModel->categories()); } else if (modifiers == Qt::ControlModifier) { QModelIndexList selection = mView->selectionModel()->selectedRows(); if (selection.count() != 1) { qCWarning(CALENDARVIEW_LOG) << "No to-do selected" << selection; return; } const QModelIndex idx = mProxyModel->mapToSource(selection[0]); mView->expand(selection[0]); const Akonadi::Item parent = sModels->todoModel->data(idx, Akonadi::EntityTreeModel::ItemRole). value<Akonadi::Item>(); addTodo(mQuickAdd->text(), parent, mProxyModel->categories()); } else { return; } mQuickAdd->setText(QString()); } void TodoView::contextMenu(const QPoint &pos) { const bool hasItem = mView->indexAt(pos).isValid(); Incidence::Ptr incidencePtr; for (QAction *entry : qAsConst(mItemPopupMenuItemOnlyEntries)) { bool enable; if (hasItem) { const Akonadi::Item::List incidences = selectedIncidences(); if (incidences.isEmpty()) { enable = false; } else { Akonadi::Item item = incidences.first(); incidencePtr = CalendarSupport::incidence(item); // Action isn't RO, it can change the incidence, "Edit" for example. const bool actionIsRw = mItemPopupMenuReadWriteEntries.contains(entry); const bool incidenceIsRO = !calendar()->hasRight(item, Akonadi::Collection::CanChangeItem); enable = hasItem && (!actionIsRw || !incidenceIsRO); } } else { enable = false; } entry->setEnabled(enable); } mCopyPopupMenu->setEnabled(hasItem); mMovePopupMenu->setEnabled(hasItem); if (hasItem) { if (incidencePtr) { const bool hasRecId = incidencePtr->hasRecurrenceId(); if (calendar()) { mMakeSubtodosIndependent->setEnabled( !hasRecId && !calendar()->childItems(incidencePtr->uid()).isEmpty()); } mMakeTodoIndependent->setEnabled(!hasRecId && !incidencePtr->relatedTo().isEmpty()); } switch (mView->indexAt(pos).column()) { case TodoModel::PriorityColumn: mPriorityPopupMenu->popup(mView->viewport()->mapToGlobal(pos)); break; case TodoModel::PercentColumn: mPercentageCompletedPopupMenu->popup(mView->viewport()->mapToGlobal(pos)); break; case TodoModel::StartDateColumn: case TodoModel::DueDateColumn: mMovePopupMenu->popup(mView->viewport()->mapToGlobal(pos)); break; case TodoModel::CategoriesColumn: createCategoryPopupMenu()->popup(mView->viewport()->mapToGlobal(pos)); break; default: mItemPopupMenu->popup(mView->viewport()->mapToGlobal(pos)); break; } } else { mItemPopupMenu->popup(mView->viewport()->mapToGlobal(pos)); } } void TodoView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { Q_UNUSED(deselected); QModelIndexList selection = selected.indexes(); if (selection.isEmpty() || !selection[0].isValid()) { Q_EMIT incidenceSelected(Akonadi::Item(), QDate()); return; } const Akonadi::Item todoItem = selection[0].data(TodoModel::TodoRole).value<Akonadi::Item>(); if (selectedIncidenceDates().isEmpty()) { Q_EMIT incidenceSelected(todoItem, QDate()); } else { Q_EMIT incidenceSelected(todoItem, selectedIncidenceDates().at(0)); } } void TodoView::showTodo() { QModelIndexList selection = mView->selectionModel()->selectedRows(); if (selection.size() != 1) { return; } const Akonadi::Item todoItem = selection[0].data(TodoModel::TodoRole).value<Akonadi::Item>(); Q_EMIT showIncidenceSignal(todoItem); } void TodoView::editTodo() { QModelIndexList selection = mView->selectionModel()->selectedRows(); if (selection.size() != 1) { return; } const Akonadi::Item todoItem = selection[0].data(TodoModel::TodoRole).value<Akonadi::Item>(); Q_EMIT editIncidenceSignal(todoItem); } void TodoView::deleteTodo() { QModelIndexList selection = mView->selectionModel()->selectedRows(); if (selection.size() == 1) { const Akonadi::Item todoItem = selection[0].data(TodoModel::TodoRole).value<Akonadi::Item>(); if (!changer()->deletedRecently(todoItem.id())) { Q_EMIT deleteIncidenceSignal(todoItem); } } } void TodoView::newTodo() { Q_EMIT newTodoSignal(QDate::currentDate().addDays(7)); } void TodoView::newSubTodo() { QModelIndexList selection = mView->selectionModel()->selectedRows(); if (selection.size() == 1) { const Akonadi::Item todoItem = selection[0].data(TodoModel::TodoRole).value<Akonadi::Item>(); Q_EMIT newSubTodoSignal(todoItem); } else { // This never happens qCWarning(CALENDARVIEW_LOG) << "Selection size isn't 1"; } } void TodoView::copyTodoToDate(const QDate &date) { if (!changer()) { return; } QModelIndexList selection = mView->selectionModel()->selectedRows(); if (selection.size() != 1) { return; } const QModelIndex origIndex = mProxyModel->mapToSource(selection[0]); Q_ASSERT(origIndex.isValid()); const Akonadi::Item origItem = sModels->todoModel->data(origIndex, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); const KCalendarCore::Todo::Ptr orig = CalendarSupport::todo(origItem); if (!orig) { return; } KCalendarCore::Todo::Ptr todo(orig->clone()); todo->setUid(KCalendarCore::CalFormat::createUniqueId()); QDateTime due = todo->dtDue(); due.setDate(date); todo->setDtDue(due); changer()->createIncidence(todo, Akonadi::Collection(), this); } void TodoView::scheduleResizeColumns() { mResizeColumnsScheduled = true; mResizeColumnsTimer->start(); // restarts the timer if already active } void TodoView::itemDoubleClicked(const QModelIndex &index) { if (index.isValid()) { QModelIndex summary = index.sibling(index.row(), TodoModel::SummaryColumn); if (summary.flags() & Qt::ItemIsEditable) { editTodo(); } else { showTodo(); } } } QMenu *TodoView::createCategoryPopupMenu() { QMenu *tempMenu = new QMenu(this); QModelIndexList selection = mView->selectionModel()->selectedRows(); if (selection.size() != 1) { return tempMenu; } const Akonadi::Item todoItem = selection[0].data(TodoModel::TodoRole).value<Akonadi::Item>(); KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(todoItem); Q_ASSERT(todo); const QStringList checkedCategories = todo->categories(); Akonadi::TagFetchJob *tagFetchJob = new Akonadi::TagFetchJob(this); connect(tagFetchJob, &Akonadi::TagFetchJob::result, this, &TodoView::onTagsFetched); tagFetchJob->setProperty("menu", QVariant::fromValue(QPointer<QMenu>(tempMenu))); tagFetchJob->setProperty("checkedCategories", checkedCategories); connect(tempMenu, &QMenu::triggered, this, &TodoView::changedCategories); connect(tempMenu, &QMenu::aboutToHide, tempMenu, &QMenu::deleteLater); return tempMenu; } void TodoView::onTagsFetched(KJob *job) { if (job->error()) { qCWarning(CALENDARVIEW_LOG) << "Failed to fetch tags " << job->errorString(); return; } Akonadi::TagFetchJob *fetchJob = static_cast<Akonadi::TagFetchJob *>(job); const QStringList checkedCategories = job->property("checkedCategories").toStringList(); QPointer<QMenu> menu = job->property("menu").value<QPointer<QMenu> >(); if (menu) { const auto lst = fetchJob->tags(); for (const Akonadi::Tag &tag : lst) { const QString name = tag.name(); QAction *action = menu->addAction(name); action->setCheckable(true); action->setData(name); if (checkedCategories.contains(name)) { action->setChecked(true); } } } } void TodoView::setNewDate(const QDate &date) { QModelIndexList selection = mView->selectionModel()->selectedRows(); if (selection.size() != 1) { return; } const Akonadi::Item todoItem = selection[0].data(TodoModel::TodoRole).value<Akonadi::Item>(); KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(todoItem); Q_ASSERT(todo); if (calendar()->hasRight(todoItem, Akonadi::Collection::CanChangeItem)) { KCalendarCore::Todo::Ptr oldTodo(todo->clone()); #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) QDateTime dt(date); #else QDateTime dt(date.startOfDay()); #endif if (!todo->allDay()) { dt.setTime(todo->dtDue().time()); } todo->setDtDue(dt); changer()->modifyIncidence(todoItem, oldTodo, this); } else { qCDebug(CALENDARVIEW_LOG) << "Item is readOnly"; } } void TodoView::setNewPercentage(QAction *action) { QModelIndexList selection = mView->selectionModel()->selectedRows(); if (selection.size() != 1) { return; } const Akonadi::Item todoItem = selection[0].data(TodoModel::TodoRole).value<Akonadi::Item>(); KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(todoItem); Q_ASSERT(todo); if (calendar()->hasRight(todoItem, Akonadi::Collection::CanChangeItem)) { KCalendarCore::Todo::Ptr oldTodo(todo->clone()); int percentage = mPercentage.value(action); if (percentage == 100) { todo->setCompleted(QDateTime::currentDateTime()); todo->setPercentComplete(100); } else { todo->setPercentComplete(percentage); } if (todo->recurs() && percentage == 100) { changer()->modifyIncidence(todoItem, oldTodo, this); } else { changer()->modifyIncidence(todoItem, oldTodo, this); } } else { qCDebug(CALENDARVIEW_LOG) << "Item is read only"; } } void TodoView::setNewPriority(QAction *action) { const QModelIndexList selection = mView->selectionModel()->selectedRows(); if (selection.size() != 1) { return; } const Akonadi::Item todoItem = selection[0].data(TodoModel::TodoRole).value<Akonadi::Item>(); KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(todoItem); if (calendar()->hasRight(todoItem, Akonadi::Collection::CanChangeItem)) { KCalendarCore::Todo::Ptr oldTodo(todo->clone()); todo->setPriority(mPriority[action]); changer()->modifyIncidence(todoItem, oldTodo, this); } } void TodoView::changedCategories(QAction *action) { const QModelIndexList selection = mView->selectionModel()->selectedRows(); if (selection.size() != 1) { return; } const Akonadi::Item todoItem = selection[0].data(TodoModel::TodoRole).value<Akonadi::Item>(); KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(todoItem); Q_ASSERT(todo); if (calendar()->hasRight(todoItem, Akonadi::Collection::CanChangeItem)) { KCalendarCore::Todo::Ptr oldTodo(todo->clone()); const QString cat = action->data().toString(); QStringList categories = todo->categories(); if (categories.contains(cat)) { categories.removeAll(cat); } else { categories.append(cat); } categories.sort(); todo->setCategories(categories); changer()->modifyIncidence(todoItem, oldTodo, this); } else { qCDebug(CALENDARVIEW_LOG) << "No active item, active item is read-only, or locking failed"; } } void TodoView::setFullView(bool fullView) { if (!mFullViewButton) { return; } mFullViewButton->setChecked(fullView); if (fullView) { mFullViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-restore"))); } else { mFullViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen"))); } mFullViewButton->blockSignals(true); // We block signals to avoid recursion; there are two TodoViews and // also mFullViewButton is synchronized. mFullViewButton->setChecked(fullView); mFullViewButton->blockSignals(false); preferences()->setFullViewTodo(fullView); preferences()->writeConfig(); Q_EMIT fullViewChanged(fullView); } void TodoView::setFlatView(bool flatView, bool notifyOtherViews) { if (flatView) { mFlatViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree"))); } else { mFlatViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-list-details"))); } if (notifyOtherViews) { sModels->setFlatView(flatView); } } void TodoView::onRowsInserted(const QModelIndex &parent, int start, int end) { if (start != end || !calendar() || !calendar()->entityTreeModel()) { return; } QModelIndex idx = mView->model()->index(start, 0); // If the collection is currently being populated, we don't do anything QVariant v = idx.data(Akonadi::EntityTreeModel::ItemRole); if (!v.isValid()) { return; } Akonadi::Item item = v.value<Akonadi::Item>(); if (!item.isValid()) { return; } const bool isPopulated = calendar()->entityTreeModel()->isCollectionPopulated( item.storageCollectionId()); if (!isPopulated) { return; } // Case #1, adding an item that doesn't have parent: We select it if (!parent.isValid()) { QModelIndexList selection = mView->selectionModel()->selectedRows(); if (selection.size() <= 1) { // don't destroy complex selections, not applicable now (only single // selection allowed), but for the future... int colCount = static_cast<int>(TodoModel::ColumnCount); mView->selectionModel()->select( QItemSelection(idx, mView->model()->index(start, colCount - 1)), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } return; } // Case 2: Adding an item that has a parent: we expand the parent if (sModels->isFlatView()) { return; } QModelIndex index = parent; mView->expand(index); while (index.parent().isValid()) { mView->expand(index.parent()); index = index.parent(); } } void TodoView::getHighlightMode(bool &highlightEvents, bool &highlightTodos, bool &highlightJournals) { highlightTodos = preferences()->highlightTodos(); highlightEvents = !highlightTodos; highlightJournals = false; } bool TodoView::usesFullWindow() { return preferences()->fullViewTodo(); } void TodoView::resizeColumns() { mResizeColumnsScheduled = false; mView->resizeColumnToContents(TodoModel::StartDateColumn); mView->resizeColumnToContents(TodoModel::DueDateColumn); mView->resizeColumnToContents(TodoModel::PriorityColumn); mView->resizeColumnToContents(TodoModel::CalendarColumn); mView->resizeColumnToContents(TodoModel::RecurColumn); mView->resizeColumnToContents(TodoModel::PercentColumn); // We have 3 columns that should stretch: summary, description and categories. // Summary is always visible. const bool descriptionVisible = !mView->isColumnHidden(TodoModel::DescriptionColumn); const bool categoriesVisible = !mView->isColumnHidden(TodoModel::CategoriesColumn); // Calculate size of non-stretchable columns: int size = 0; for (int i = 0; i < TodoModel::ColumnCount; ++i) { if (!mView->isColumnHidden(i) && i != TodoModel::SummaryColumn && i != TodoModel::DescriptionColumn && i != TodoModel::CategoriesColumn) { size += mView->columnWidth(i); } } // Calculate the remaining space that we have for the stretchable columns int remainingSize = mView->header()->width() - size; // 100 for summary, 100 for description const int requiredSize = descriptionVisible ? 200 : 100; if (categoriesVisible) { const int categorySize = 100; mView->setColumnWidth(TodoModel::CategoriesColumn, categorySize); remainingSize -= categorySize; } if (remainingSize < requiredSize) { // Too little size, so let's use a horizontal scrollbar mView->resizeColumnToContents(TodoModel::SummaryColumn); mView->resizeColumnToContents(TodoModel::DescriptionColumn); } else if (descriptionVisible) { mView->setColumnWidth(TodoModel::SummaryColumn, remainingSize / 2); mView->setColumnWidth(TodoModel::DescriptionColumn, remainingSize / 2); } else { mView->setColumnWidth(TodoModel::SummaryColumn, remainingSize); } } void TodoView::restoreViewState() { if (sModels->isFlatView()) { return; } if (sModels->todoTreeModel && !sModels->todoTreeModel->sourceModel()) { return; } //QElapsedTimer timer; //timer.start(); delete mTreeStateRestorer; mTreeStateRestorer = new Akonadi::ETMViewStateSaver(); KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group(config, stateSaverGroup()); mTreeStateRestorer->setView(mView); mTreeStateRestorer->restoreState(group); //qCDebug(CALENDARVIEW_LOG) << "Took " << timer.elapsed(); } QString TodoView::stateSaverGroup() const { QString str = QStringLiteral("TodoTreeViewState"); if (mSidebarView) { str += QLatin1Char('S'); } return str; } void TodoView::saveViewState() { Akonadi::ETMViewStateSaver treeStateSaver; KConfigGroup group(preferences()->config(), stateSaverGroup()); treeStateSaver.setView(mView); treeStateSaver.saveState(group); } void TodoView::resizeEvent(QResizeEvent *event) { EventViews::EventView::resizeEvent(event); scheduleResizeColumns(); } void TodoView::createEvent() { const QModelIndexList selection = mView->selectionModel()->selectedRows(); if (selection.size() != 1) { return; } const Akonadi::Item todoItem = selection[0].data(TodoModel::TodoRole).value<Akonadi::Item>(); Q_EMIT createEvent(todoItem); } void TodoView::createNote() { const QModelIndexList selection = mView->selectionModel()->selectedRows(); if (selection.size() != 1) { return; } const Akonadi::Item todoItem = selection[0].data(TodoModel::TodoRole).value<Akonadi::Item>(); Q_EMIT createNote(todoItem); } 07070100000051000081A40000000200000002000000015F0BF3C9000017ED000000000000000000000000000000000000004200000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/todoview.h/* This file is part of KOrganizer. Copyright (c) 2000,2001,2003 Cornelius Schumacher <schumacher@kde.org> Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> Copyright (c) 2005 Rafal Rzepecki <divide@users.sourceforge.net> Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef CALENDARVIEWS_TODOVIEW_H #define CALENDARVIEWS_TODOVIEW_H #include "eventview.h" #include <Akonadi/Calendar/ETMCalendar> #include <Akonadi/Calendar/IncidenceChanger> #include <KConfig> #include <QPointer> class KJob; class TodoCategoriesDelegate; class TodoViewQuickAddLine; class TodoViewQuickSearch; class TodoViewSortFilterProxyModel; class TodoViewView; namespace CalendarSupport { class KDatePickerPopup; } namespace Akonadi { class ETMViewStateSaver; } class QItemSelection; class QMenu; class QModelIndex; class QToolButton; class QTimer; namespace EventViews { class EVENTVIEWS_EXPORT TodoView : public EventViews::EventView { Q_OBJECT friend class ModelStack; public: TodoView(const EventViews::PrefsPtr &preferences, bool sidebarView, QWidget *parent); ~TodoView() override; void setCalendar(const Akonadi::ETMCalendar::Ptr &) override; Q_REQUIRED_RESULT Akonadi::Item::List selectedIncidences() const override; Q_REQUIRED_RESULT KCalendarCore::DateList selectedIncidenceDates() const override; Q_REQUIRED_RESULT int currentDateCount() const override { return 0; } void setDocumentId(const QString &) { } void saveLayout(KConfig *config, const QString &group) const; void restoreLayout(KConfig *config, const QString &group, bool minimalDefaults); /** documentation in baseview.h */ void getHighlightMode(bool &highlightEvents, bool &highlightTodos, bool &highlightJournals); Q_REQUIRED_RESULT bool usesFullWindow(); Q_REQUIRED_RESULT bool supportsDateRangeSelection() const { return false; } public Q_SLOTS: void setIncidenceChanger(Akonadi::IncidenceChanger *changer) override; void showDates(const QDate &start, const QDate &end, const QDate &preferredMonth = QDate()) override; void showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) override; void updateView() override; virtual void changeIncidenceDisplay(const Akonadi::Item &incidence, Akonadi::IncidenceChanger::ChangeType changeType); void updateConfig() override; void clearSelection() override; void expandIndex(const QModelIndex &index); void restoreViewState(); void saveViewState(); void createNote(); void createEvent(); protected Q_SLOTS: void resizeEvent(QResizeEvent *) override; void addQuickTodo(Qt::KeyboardModifiers modifier); void contextMenu(const QPoint &pos); void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); // slots used by popup-menus void showTodo(); void editTodo(); void deleteTodo(); void newTodo(); void newSubTodo(); void copyTodoToDate(const QDate &date); private Q_SLOTS: void scheduleResizeColumns(); void resizeColumns(); void itemDoubleClicked(const QModelIndex &index); void setNewDate(const QDate &date); void setNewPercentage(QAction *action); void setNewPriority(QAction *action); void changedCategories(QAction *action); void setFullView(bool fullView); void setFlatView(bool flatView, bool notifyOtherViews = true); void onRowsInserted(const QModelIndex &parent, int start, int end); void onTagsFetched(KJob *); Q_SIGNALS: void purgeCompletedSignal(); void unSubTodoSignal(); void unAllSubTodoSignal(); void configChanged(); void fullViewChanged(bool enabled); void printPreviewTodo(); void printTodo(); void createNote(const Akonadi::Item &item); void createEvent(const Akonadi::Item &item); private: QMenu *createCategoryPopupMenu(); QString stateSaverGroup() const; /** Creates a new todo with the given text as summary under the given parent */ void addTodo(const QString &summary, const Akonadi::Item &parentItem, const QStringList &categories = QStringList()); TodoViewView *mView = nullptr; TodoViewSortFilterProxyModel *mProxyModel = nullptr; TodoCategoriesDelegate *mCategoriesDelegate = nullptr; TodoViewQuickSearch *mQuickSearch = nullptr; TodoViewQuickAddLine *mQuickAdd = nullptr; QToolButton *mFullViewButton = nullptr; QToolButton *mFlatViewButton = nullptr; QMenu *mItemPopupMenu = nullptr; CalendarSupport::KDatePickerPopup *mCopyPopupMenu = nullptr; CalendarSupport::KDatePickerPopup *mMovePopupMenu = nullptr; QMenu *mPriorityPopupMenu = nullptr; QMenu *mPercentageCompletedPopupMenu = nullptr; QList<QAction *> mItemPopupMenuItemOnlyEntries; QList<QAction *> mItemPopupMenuReadWriteEntries; QAction *mMakeTodoIndependent = nullptr; QAction *mMakeSubtodosIndependent = nullptr; QPointer<Akonadi::ETMViewStateSaver> mTreeStateRestorer; QMap<QAction *, int> mPercentage; QMap<QAction *, int> mPriority; bool mSidebarView; bool mResizeColumnsScheduled; QTimer *mResizeColumnsTimer = nullptr; }; } #endif 07070100000052000081A40000000200000002000000015F0BF3C9000007EA000000000000000000000000000000000000005000000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/todoviewquickaddline.cpp/* This file is part of KOrganizer. Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "todoviewquickaddline.h" #include <KLocalizedString> #include <QKeyEvent> TodoViewQuickAddLine::TodoViewQuickAddLine(QWidget *parent) : KLineEdit(parent) { connect(this, SIGNAL(returnPressed()), this, SLOT(returnPressedSlot())); mClickMessage = i18n("Enter a summary to create a new to-do"); setToolTip(mClickMessage); } void TodoViewQuickAddLine::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Return) { mModifiers = event->modifiers(); } KLineEdit::keyPressEvent(event); } void TodoViewQuickAddLine::returnPressedSlot() { // Workaround bug #217592 (disappearing cursor) unsetCursor(); Q_EMIT returnPressed(mModifiers); } void TodoViewQuickAddLine::resizeEvent(QResizeEvent *event) { KLineEdit::resizeEvent(event); setPlaceholderText(fontMetrics().elidedText( mClickMessage, Qt::ElideRight, width() - clearButtonUsedSize().width())); } 07070100000053000081A40000000200000002000000015F0BF3C900000646000000000000000000000000000000000000004E00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/todoviewquickaddline.h/* This file is part of KOrganizer. Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef CALENDARVIEWS_TODOVIEWQUICKADDLINE_H #define CALENDARVIEWS_TODOVIEWQUICKADDLINE_H #include <KLineEdit> class TodoViewQuickAddLine : public KLineEdit { Q_OBJECT public: explicit TodoViewQuickAddLine(QWidget *parent); ~TodoViewQuickAddLine() override { } protected: void keyPressEvent(QKeyEvent *event) override; void resizeEvent(QResizeEvent *event) override; Q_SIGNALS: void returnPressed(Qt::KeyboardModifiers modifiers); private Q_SLOTS: void returnPressedSlot(); private: Qt::KeyboardModifiers mModifiers; QString mClickMessage; }; #endif 07070100000054000081A40000000200000002000000015F0BF3C900001666000000000000000000000000000000000000004F00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/todoviewquicksearch.cpp/* This file is part of KOrganizer. Copyright (c) 2004 Till Adam <adam@kde.org> Copyright (c) 2005 Rafal Rzepecki <divide@users.sourceforge.net> Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "todoviewquicksearch.h" #include <CalendarSupport/KCalPrefs> #include <AkonadiWidgets/TagSelectionComboBox> #include <Akonadi/Calendar/ETMCalendar> #include <CalendarSupport/CategoryConfig> #include <Libkdepim/KCheckComboBox> #include <CalendarSupport/CategoryHierarchyReader> #include <KCalendarCore/CalFilter> #include <QLineEdit> #include <KLocalizedString> #include <QHBoxLayout> TodoViewQuickSearch::TodoViewQuickSearch(const Akonadi::ETMCalendar::Ptr &calendar, QWidget *parent) : QWidget(parent) , mCalendar(calendar) { QHBoxLayout *layout = new QHBoxLayout(this); // no special margin because it is added by the view layout->setContentsMargins(0, 0, 0, 0); mSearchLine = new QLineEdit(this); mSearchLine->setToolTip( i18nc("@info:tooltip", "Filter on matching summaries")); mSearchLine->setWhatsThis( i18nc("@info:whatsthis", "Enter text here to filter the to-dos that are shown by matching summaries.")); mSearchLine->setPlaceholderText(i18nc("@label in QuickSearchLine", "Search Summaries...")); mSearchLine->setClearButtonEnabled(true); connect(mSearchLine, &QLineEdit::textChanged, this, &TodoViewQuickSearch::searchTextChanged); layout->addWidget(mSearchLine, 3); mCategoryCombo = new Akonadi::TagSelectionComboBox(this); mCategoryCombo->setCheckable(true); mCategoryCombo->setToolTip( i18nc("@info:tooltip", "Filter on these categories")); mCategoryCombo->setWhatsThis( i18nc("@info:whatsthis", "Use this combobox to filter the to-dos that are shown by " "a list of selected categories.")); const QString defaultText = i18nc("@item:inlistbox", "Select Categories"); mCategoryCombo->lineEdit()->setPlaceholderText(defaultText); connect(mCategoryCombo, &Akonadi::TagSelectionComboBox::selectionChanged, this, [this]() { Q_EMIT filterCategoryChanged(mCategoryCombo->selectionNames()); }); layout->addWidget(mCategoryCombo, 1); { // Make the combo big enough so that "Select Categories" fits. QFontMetrics fm = mCategoryCombo->lineEdit()->fontMetrics(); // QLineEdit::sizeHint() returns a nice size to fit 17 'x' chars. const int currentPreferedWidth = mCategoryCombo->lineEdit()->sizeHint().width(); // Calculate a nice size for "Select Categories" const int newPreferedWidth = currentPreferedWidth -fm.boundingRect(QLatin1Char('x')).width() * 17 +fm.boundingRect(defaultText).width(); const int pixelsToAdd = newPreferedWidth - mCategoryCombo->lineEdit()->width(); mCategoryCombo->setMinimumWidth(mCategoryCombo->width() + pixelsToAdd); } mPriorityCombo = new KPIM::KCheckComboBox(this); mPriorityCombo->setToolTip( i18nc("@info:tooltip", "Filter on these priorities")); mPriorityCombo->setWhatsThis( i18nc("@info:whatsthis", "Use this combobox to filter the to-dos that are shown by " "a list of selected priorities.")); mPriorityCombo->setDefaultText(i18nc("@item:inlistbox", "Select Priority")); connect(mPriorityCombo, &KPIM::KCheckComboBox::checkedItemsChanged, this, [this]() { Q_EMIT filterPriorityChanged(mPriorityCombo->checkedItems(Qt::UserRole)); }); layout->addWidget(mPriorityCombo, 1); fillPriorities(); } void TodoViewQuickSearch::setCalendar(const Akonadi::ETMCalendar::Ptr &calendar) { if (calendar != mCalendar) { mCalendar = calendar; } } void TodoViewQuickSearch::reset() { mSearchLine->clear(); mCategoryCombo->setCurrentIndex(0); mPriorityCombo->setCurrentIndex(0); } void TodoViewQuickSearch::fillPriorities() { QStringList priorityValues; priorityValues.append(i18nc("@action:inmenu priority is unspecified", "unspecified")); priorityValues.append(i18nc("@action:inmenu highest priority", "%1 (highest)", 1)); for (int p = 2; p < 10; ++p) { if (p == 5) { priorityValues.append(i18nc("@action:inmenu medium priority", "%1 (medium)", p)); } else if (p == 9) { priorityValues.append(i18nc("@action:inmenu lowest priority", "%1 (lowest)", p)); } else { priorityValues.append(i18nc("@action:inmenu", "%1", p)); } } // TODO: Using the same method as for categories to fill mPriorityCombo CalendarSupport::CategoryHierarchyReaderQComboBox(mPriorityCombo).read(priorityValues); } 07070100000055000081A40000000200000002000000015F0BF3C900000967000000000000000000000000000000000000004D00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/todoviewquicksearch.h/* This file is part of KOrganizer. Copyright (c) 2004 Till Adam <adam@kde.org> Copyright (c) 2005 Rafal Rzepecki <divide@users.sourceforge.net> Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef CALENDARVIEWS_TODOVIEWQUICKSEARCH_H #define CALENDARVIEWS_TODOVIEWQUICKSEARCH_H #include <Akonadi/Calendar/ETMCalendar> #include <QWidget> namespace KPIM { class KCheckComboBox; } namespace Akonadi { class TagSelectionComboBox; } class QLineEdit; class TodoViewQuickSearch : public QWidget { Q_OBJECT public: TodoViewQuickSearch(const Akonadi::ETMCalendar::Ptr &calendar, QWidget *parent); void setCalendar(const Akonadi::ETMCalendar::Ptr &calendar); Q_SIGNALS: void searchTextChanged(const QString &); /** * The string list contains the new categories which are set on the filter. * All values belong to the Qt::UserRole of the combo box, not the Qt::DisplayRole, * so, if someone checks a subcategory, the value will be "ParentCategory:subCategory" * and not " subcategory". * */ void filterCategoryChanged(const QStringList &); void filterPriorityChanged(const QStringList &); public Q_SLOTS: void reset(); private: /** Helper method for the filling of the priority combo. */ void fillPriorities(); Akonadi::ETMCalendar::Ptr mCalendar; QLineEdit *mSearchLine = nullptr; Akonadi::TagSelectionComboBox *mCategoryCombo = nullptr; KPIM::KCheckComboBox *mPriorityCombo = nullptr; }; #endif 07070100000056000081A40000000200000002000000015F0BF3C9000031F5000000000000000000000000000000000000005800000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/todoviewsortfilterproxymodel.cpp/* This file is part of KOrganizer. Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "todoviewsortfilterproxymodel.h" #include "todomodel.h" #include <CalendarSupport/Utils> #include <KLocalizedString> TodoViewSortFilterProxyModel::TodoViewSortFilterProxyModel(const EventViews::PrefsPtr &prefs, QObject *parent) : QSortFilterProxyModel(parent) , mSortOrder(Qt::AscendingOrder) , mPreferences(prefs) { } void TodoViewSortFilterProxyModel::sort(int column, Qt::SortOrder order) { mSortOrder = order; QSortFilterProxyModel::sort(column, order); } bool TodoViewSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { bool ret = QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); bool returnValue = true; if (ret && !mPriorities.isEmpty()) { QString priorityValue = sourceModel()->index(source_row, TodoModel::PriorityColumn, source_parent). data(Qt::EditRole).toString(); returnValue = mPriorities.contains(priorityValue); } if (ret && !mCategories.isEmpty()) { const QStringList categories = sourceModel()->index(source_row, TodoModel::CategoriesColumn, source_parent). data(Qt::EditRole).toStringList(); for (const QString &category : categories) { if (mCategories.contains(category)) { return returnValue && true; } } ret = false; } // check if one of the children is accepted, and accept this node too if so QModelIndex cur = sourceModel()->index(source_row, TodoModel::SummaryColumn, source_parent); if (cur.isValid()) { for (int r = 0; r < cur.model()->rowCount(cur); ++r) { if (filterAcceptsRow(r, cur)) { return true; } } } return ret && returnValue; } bool TodoViewSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { if (mPreferences->sortCompletedTodosSeparately() && left.column() != TodoModel::PercentColumn) { QModelIndex cLeft = left.sibling(left.row(), TodoModel::PercentColumn); QModelIndex cRight = right.sibling(right.row(), TodoModel::PercentColumn); if (cRight.data(Qt::EditRole).toInt() == 100 && cLeft.data(Qt::EditRole).toInt() != 100) { return mSortOrder == Qt::AscendingOrder ? true : false; } else if (cRight.data(Qt::EditRole).toInt() != 100 && cLeft.data(Qt::EditRole).toInt() == 100) { return mSortOrder == Qt::AscendingOrder ? false : true; } } // To-dos without due date should appear last when sorting ascending, // so you can see the most urgent tasks first. (bug #174763) if (right.column() == TodoModel::DueDateColumn) { const int comparison = compareDueDates(left, right); if (comparison != 0) { return comparison == -1; } else { // Due dates are equal, but the user still expects sorting by importance // Fallback to the PriorityColumn QModelIndex leftPriorityIndex = left.sibling(left.row(), TodoModel::PriorityColumn); QModelIndex rightPriorityIndex = right.sibling(right.row(), TodoModel::PriorityColumn); const int fallbackComparison = comparePriorities(leftPriorityIndex, rightPriorityIndex); if (fallbackComparison != 0) { return fallbackComparison == 1; } } } else if (right.column() == TodoModel::StartDateColumn) { return compareStartDates(left, right) == -1; } else if (right.column() == TodoModel::PriorityColumn) { const int comparison = comparePriorities(left, right); if (comparison != 0) { return comparison == -1; } else { // Priorities are equal, but the user still expects sorting by importance // Fallback to the DueDateColumn QModelIndex leftDueDateIndex = left.sibling(left.row(), TodoModel::DueDateColumn); QModelIndex rightDueDateIndex = right.sibling(right.row(), TodoModel::DueDateColumn); const int fallbackComparison = compareDueDates(leftDueDateIndex, rightDueDateIndex); if (fallbackComparison != 0) { return fallbackComparison == 1; } } } else if (right.column() == TodoModel::PercentColumn) { const int comparison = compareCompletion(left, right); if (comparison != 0) { return comparison == -1; } } if (left.data() == right.data()) { // If both are equal, lets choose an order, otherwise Qt will display them randomly. // Fixes to-dos jumping around when you have calendar A selected, and then check/uncheck // a calendar B with no to-dos. No to-do is added/removed because calendar B is empty, // but you see the existing to-dos switching places. QModelIndex leftSummaryIndex = left.sibling(left.row(), TodoModel::SummaryColumn); QModelIndex rightSummaryIndex = right.sibling(right.row(), TodoModel::SummaryColumn); // This patch is not about fallingback to the SummaryColumn for sorting. // It's about avoiding jumping due to random reasons. // That's why we ignore the sort direction... return mSortOrder == Qt::AscendingOrder ? QSortFilterProxyModel::lessThan(leftSummaryIndex, rightSummaryIndex) : QSortFilterProxyModel::lessThan(rightSummaryIndex, leftSummaryIndex); // ...so, if you have 4 to-dos, all with CompletionColumn = "55%", // and click the header multiple times, nothing will happen because // it is already sorted by Completion. } else { return QSortFilterProxyModel::lessThan(left, right); } } void TodoViewSortFilterProxyModel::setPriorityFilter(const QStringList &priorities) { // preparing priority list for comparison mPriorities.clear(); for (const QString &eachPriority : priorities) { if (eachPriority == i18nc("priority is unspecified", "unspecified")) { mPriorities.append(i18n("%1", 0)); } else if (eachPriority == i18nc("highest priority", "%1 (highest)", 1)) { mPriorities.append(i18n("%1", 1)); } else if (eachPriority == i18nc("medium priority", "%1 (medium)", 5)) { mPriorities.append(i18n("%1", 5)); } else if (eachPriority == i18nc("lowest priority", "%1 (lowest)", 9)) { mPriorities.append(i18n("%1", 9)); } else { mPriorities.append(eachPriority); } } invalidateFilter(); } int TodoViewSortFilterProxyModel::compareStartDates(const QModelIndex &left, const QModelIndex &right) const { Q_ASSERT(left.column() == TodoModel::StartDateColumn); Q_ASSERT(right.column() == TodoModel::StartDateColumn); // The due date column is a QString so fetch the akonadi item // We can't compare QStrings because it won't work if the format is MM/DD/YYYY const KCalendarCore::Todo::Ptr leftTodo = CalendarSupport::todo(left.data(TodoModel::TodoRole).value<Akonadi::Item>()); const KCalendarCore::Todo::Ptr rightTodo = CalendarSupport::todo(right.data(TodoModel::TodoRole).value<Akonadi::Item>()); if (!leftTodo || !rightTodo) { return 0; } const bool leftIsEmpty = !leftTodo->hasStartDate(); const bool rightIsEmpty = !rightTodo->hasStartDate(); if (leftIsEmpty != rightIsEmpty) { // One of them doesn't have a start date // For sorting, no date is considered a very big date return rightIsEmpty ? -1 : 1; } else if (!leftIsEmpty) { // Both have start dates const auto leftDateTime = leftTodo->dtStart(); const auto rightDateTime = rightTodo->dtStart(); if (leftDateTime == rightDateTime) { return 0; } else { return leftDateTime < rightDateTime ? -1 : 1; } } else { // Neither has a start date return 0; } } void TodoViewSortFilterProxyModel::setCategoryFilter(const QStringList &categories) { mCategories = categories; invalidateFilter(); } /* -1 - less than * 0 - equal * 1 - bigger than */ int TodoViewSortFilterProxyModel::compareDueDates(const QModelIndex &left, const QModelIndex &right) const { Q_ASSERT(left.column() == TodoModel::DueDateColumn); Q_ASSERT(right.column() == TodoModel::DueDateColumn); // The due date column is a QString so fetch the akonadi item // We can't compare QStrings because it won't work if the format is MM/DD/YYYY const KCalendarCore::Todo::Ptr leftTodo = CalendarSupport::todo(left.data(TodoModel::TodoRole).value<Akonadi::Item>()); const KCalendarCore::Todo::Ptr rightTodo = CalendarSupport::todo(right.data(TodoModel::TodoRole).value<Akonadi::Item>()); if (!leftTodo || !rightTodo) { return 0; } const bool leftIsEmpty = !leftTodo->hasDueDate(); const bool rightIsEmpty = !rightTodo->hasDueDate(); if (leftIsEmpty != rightIsEmpty) { // One of them doesn't have a due date // For sorting, no date is considered a very big date return rightIsEmpty ? -1 : 1; } else if (!leftIsEmpty) { // Both have due dates const auto leftDateTime = leftTodo->dtDue(); const auto rightDateTime = rightTodo->dtDue(); if (leftDateTime == rightDateTime) { return 0; } else { return leftDateTime < rightDateTime ? -1 : 1; } } else { // Neither has a due date return 0; } } /* -1 - less than * 0 - equal * 1 - bigger than */ int TodoViewSortFilterProxyModel::compareCompletion(const QModelIndex &left, const QModelIndex &right) const { Q_ASSERT(left.column() == TodoModel::PercentColumn); Q_ASSERT(right.column() == TodoModel::PercentColumn); const int leftValue = sourceModel()->data(left).toInt(); const int rightValue = sourceModel()->data(right).toInt(); if (leftValue == 100 && rightValue == 100) { // Untie with the completion date const KCalendarCore::Todo::Ptr leftTodo = CalendarSupport::todo(left.data(TodoModel::TodoRole).value<Akonadi::Item>()); const KCalendarCore::Todo::Ptr rightTodo = CalendarSupport::todo(right.data(TodoModel::TodoRole).value<Akonadi::Item>()); if (!leftTodo || !rightTodo) { return 0; } else { return (leftTodo->completed() > rightTodo->completed()) ? -1 : 1; } } else { return (leftValue < rightValue) ? -1 : 1; } } /* -1 - less than * 0 - equal * 1 - bigger than */ int TodoViewSortFilterProxyModel::comparePriorities(const QModelIndex &left, const QModelIndex &right) const { Q_ASSERT(left.column() == TodoModel::PriorityColumn); Q_ASSERT(right.column() == TodoModel::PriorityColumn); const QVariant leftValue = sourceModel()->data(left); const QVariant rightValue = sourceModel()->data(right); const bool leftIsString = sourceModel()->data(left).type() == QVariant::String; const bool rightIsString = sourceModel()->data(right).type() == QVariant::String; // unspecified priority is a low priority, so, if we don't have two QVariant:Ints // we return true ("left is less, i.e. higher prio") if right is a string ("--"). if (leftIsString != rightIsString) { return leftIsString ? -1 : 1; } else { const int leftPriority = leftValue.toInt(); const int rightPriority = rightValue.toInt(); if (leftPriority != rightPriority) { // priority '5' is bigger then priroity '6' return leftPriority < rightPriority ? 1 : -1; } else { return 0; } } } 07070100000057000081A40000000200000002000000015F0BF3C9000009A6000000000000000000000000000000000000005600000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/todoviewsortfilterproxymodel.h/* This file is part of KOrganizer. Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef CALENDARVIEWS_TODOVIEWSORTFILTERPROXYMODEL_H #define CALENDARVIEWS_TODOVIEWSORTFILTERPROXYMODEL_H #include "prefs.h" #include <QSortFilterProxyModel> #include <QStringList> class TodoViewSortFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: explicit TodoViewSortFilterProxyModel(const EventViews::PrefsPtr &prefs, QObject *parent = nullptr); void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; Q_REQUIRED_RESULT const QStringList &categories() const { return mCategories; } Q_REQUIRED_RESULT const QStringList &priorities() const { return mPriorities; } protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; public Q_SLOTS: void setCategoryFilter(const QStringList &categories); void setPriorityFilter(const QStringList &priorities); private: int compareStartDates(const QModelIndex &left, const QModelIndex &right) const; int compareDueDates(const QModelIndex &left, const QModelIndex &right) const; int comparePriorities(const QModelIndex &left, const QModelIndex &right) const; int compareCompletion(const QModelIndex &left, const QModelIndex &right) const; QStringList mCategories; QStringList mPriorities; Qt::SortOrder mSortOrder; EventViews::PrefsPtr mPreferences; }; #endif 07070100000058000081A40000000200000002000000015F0BF3C900001C07000000000000000000000000000000000000004800000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/todoviewview.cpp/* This file is part of KOrganizer. Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "todoviewview.h" #include <KLocalizedString> #include <QMenu> #include <QAction> #include <QContextMenuEvent> #include <QEvent> #include <QHeaderView> #include <QMouseEvent> TodoViewView::TodoViewView(QWidget *parent) : QTreeView(parent) , mHeaderPopup(nullptr) , mIgnoreNextMouseRelease(false) { header()->installEventFilter(this); setAlternatingRowColors(true); connect(&mExpandTimer, &QTimer::timeout, this, &TodoViewView::expandParent); mExpandTimer.setInterval(1000); header()->setStretchLastSection(false); } bool TodoViewView::isEditing(const QModelIndex &index) const { return state() & QAbstractItemView::EditingState && currentIndex() == index; } bool TodoViewView::eventFilter(QObject *watched, QEvent *event) { Q_UNUSED(watched); if (event->type() == QEvent::ContextMenu) { QContextMenuEvent *e = static_cast<QContextMenuEvent *>(event); if (!mHeaderPopup) { mHeaderPopup = new QMenu(this); mHeaderPopup->setTitle(i18n("View Columns")); // First entry can't be disabled for (int i = 1; i < model()->columnCount(); ++i) { QAction *tmp = mHeaderPopup->addAction(model()->headerData(i, Qt::Horizontal).toString()); tmp->setData(QVariant(i)); tmp->setCheckable(true); mColumnActions << tmp; } connect(mHeaderPopup, &QMenu::triggered, this, &TodoViewView::toggleColumnHidden); } for (QAction *action : qAsConst(mColumnActions)) { int column = action->data().toInt(); action->setChecked(!isColumnHidden(column)); } mHeaderPopup->popup(mapToGlobal(e->pos())); return true; } return false; } void TodoViewView::toggleColumnHidden(QAction *action) { if (action->isChecked()) { showColumn(action->data().toInt()); } else { hideColumn(action->data().toInt()); } Q_EMIT visibleColumnCountChanged(); } QModelIndex TodoViewView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) { QModelIndex current = currentIndex(); if (!current.isValid()) { return QTreeView::moveCursor(cursorAction, modifiers); } switch (cursorAction) { case MoveNext: { // try to find an editable item right of the current one QModelIndex tmp = getNextEditableIndex( current.sibling(current.row(), current.column() + 1), 1); if (tmp.isValid()) { return tmp; } // check if the current item is expanded, and find an editable item // just below it if so current = current.sibling(current.row(), 0); if (isExpanded(current)) { tmp = getNextEditableIndex(model()->index(0, 0, current), 1); if (tmp.isValid()) { return tmp; } } // find an editable item in the item below the currently edited one tmp = getNextEditableIndex(current.sibling(current.row() + 1, 0), 1); if (tmp.isValid()) { return tmp; } // step back a hierarchy level, and search for an editable item there while (current.isValid()) { current = current.parent(); tmp = getNextEditableIndex(current.sibling(current.row() + 1, 0), 1); if (tmp.isValid()) { return tmp; } } return QModelIndex(); } case MovePrevious: { // try to find an editable item left of the current one QModelIndex tmp = getNextEditableIndex( current.sibling(current.row(), current.column() - 1), -1); if (tmp.isValid()) { return tmp; } int lastCol = model()->columnCount(QModelIndex()) - 1; // search on top of the item, also include expanded items tmp = current.sibling(current.row() - 1, 0); while (tmp.isValid() && isExpanded(tmp)) { tmp = model()->index(model()->rowCount(tmp) - 1, 0, tmp); } if (tmp.isValid()) { tmp = getNextEditableIndex(tmp.sibling(tmp.row(), lastCol), -1); if (tmp.isValid()) { return tmp; } } // step back a hierarchy level, and search for an editable item there current = current.parent(); return getNextEditableIndex(current.sibling(current.row(), lastCol), -1); } default: break; } return QTreeView::moveCursor(cursorAction, modifiers); } QModelIndex TodoViewView::getNextEditableIndex(const QModelIndex &cur, int inc) { if (!cur.isValid()) { return QModelIndex(); } QModelIndex tmp; int colCount = model()->columnCount(QModelIndex()); int end = inc == 1 ? colCount : -1; for (int c = cur.column(); c != end; c += inc) { tmp = cur.sibling(cur.row(), c); if ((tmp.flags() & Qt::ItemIsEditable) && !isIndexHidden(tmp)) { return tmp; } } return QModelIndex(); } void TodoViewView::mouseReleaseEvent(QMouseEvent *event) { mExpandTimer.stop(); if (mIgnoreNextMouseRelease) { mIgnoreNextMouseRelease = false; return; } if (!indexAt(event->pos()).isValid()) { clearSelection(); event->accept(); } else { QTreeView::mouseReleaseEvent(event); } } void TodoViewView::mouseMoveEvent(QMouseEvent *event) { mExpandTimer.stop(); QTreeView::mouseMoveEvent(event); } void TodoViewView::mousePressEvent(QMouseEvent *event) { mExpandTimer.stop(); QModelIndex index = indexAt(event->pos()); if (index.isValid() && event->button() == Qt::LeftButton) { mExpandTimer.start(); } QTreeView::mousePressEvent(event); } void TodoViewView::expandParent() { QModelIndex index = indexAt(viewport()->mapFromGlobal(QCursor::pos())); if (index.isValid()) { mIgnoreNextMouseRelease = true; QKeyEvent keyEvent = QKeyEvent(QEvent::KeyPress, Qt::Key_Asterisk, Qt::NoModifier); QTreeView::keyPressEvent(&keyEvent); } } 07070100000059000081A40000000200000002000000015F0BF3C9000007EA000000000000000000000000000000000000004600000000eventviews-VERSIONgit.20200713T074025~752bb43/src/todo/todoviewview.h/* This file is part of KOrganizer. Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef CALENDARVIEWS_TODOVIEWVIEW_H #define CALENDARVIEWS_TODOVIEWVIEW_H #include <QTreeView> #include <QTimer> class QMenu; class TodoViewView : public QTreeView { Q_OBJECT public: explicit TodoViewView(QWidget *parent = nullptr); Q_REQUIRED_RESULT bool isEditing(const QModelIndex &index) const; Q_REQUIRED_RESULT bool eventFilter(QObject *watched, QEvent *event) override; protected: QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override; void mousePressEvent(QMouseEvent *) override; void mouseReleaseEvent(QMouseEvent *) override; void mouseMoveEvent(QMouseEvent *) override; private: QModelIndex getNextEditableIndex(const QModelIndex &cur, int inc); QMenu *mHeaderPopup = nullptr; QList<QAction *> mColumnActions; QTimer mExpandTimer; bool mIgnoreNextMouseRelease; Q_SIGNALS: void visibleColumnCountChanged(); private Q_SLOTS: void toggleColumnHidden(QAction *action); void expandParent(); }; #endif 0707010000005A000041ED0000000200000002000000025F0BF3C900000000000000000000000000000000000000000000003C00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/viewerapp0707010000005B000081A40000000200000002000000015F0BF3C900000223000000000000000000000000000000000000004B00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/viewerapp/CMakeLists.txt set(viewerapp_SRCS main.cpp mainwindow.cpp ../calendarview_debug.cpp ) kconfig_add_kcfg_files(viewerapp_SRCS settings.kcfgc) ki18n_wrap_ui(viewerapp_SRCS mainwindow.ui ) add_executable(viewerapp ${viewerapp_SRCS}) target_link_libraries(viewerapp KF5::CalendarSupport KF5::EventViews KF5::AkonadiWidgets KF5::CalendarCore KF5::AkonadiCalendar ) ########### install files ############### #install(TARGETS viewerapp ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) #install(PROGRAMS viewerapp.desktop DESTINATION ${KDE_INSTALL_APPDIR}) 0707010000005C000081A40000000200000002000000015F0BF3C900000891000000000000000000000000000000000000004500000000eventviews-VERSIONgit.20200713T074025~752bb43/src/viewerapp/main.cpp/* Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mainwindow.h" #include <KAboutData> #include <KLocalizedString> #include <QApplication> #include <QCommandLineParser> #include <QCommandLineOption> static const char description[] = I18N_NOOP("A test app for embedding calendarviews"); int main(int argc, char **argv) { KAboutData about(QStringLiteral("viewerapp"), i18n("ViewerApp"), QStringLiteral("0.1"), i18n( description), KAboutLicense::GPL, i18n( "Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net")); about.addAuthor(i18n("Kevin Krammer"), QString(), QStringLiteral("krake@kdab.com")); QCommandLineParser parser; QApplication app(argc, argv); parser.addVersionOption(); parser.addHelpOption(); about.setupCommandLine(&parser); parser.process(app); about.processCommandLine(&parser); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("+[viewname]"), i18n("Optional list of view names to instantiate"))); QStringList viewNames; for (int i = 0; i < parser.positionalArguments().count(); ++i) { viewNames << parser.positionalArguments().at(i).toLower(); } MainWindow *widget = new MainWindow(viewNames); widget->show(); return app.exec(); } 0707010000005D000081A40000000200000002000000015F0BF3C900001025000000000000000000000000000000000000004B00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/viewerapp/mainwindow.cpp/* Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mainwindow.h" #include "settings.h" #include "agenda/agenda.h" #include "agenda/agendaview.h" #include "month/monthview.h" #include "multiagenda/multiagendaview.h" #include "timeline/timelineview.h" #include "prefs.h" #include "calendarview_debug.h" #include <CalendarSupport/CollectionSelection> #include <Akonadi/Calendar/IncidenceChanger> #include <KCalendarCore/Event> #include <AkonadiCore/Collection> #include <AkonadiWidgets/ControlGui> #include <KCheckableProxyModel> using namespace Akonadi; using namespace CalendarSupport; using namespace EventViews; MainWindow::MainWindow(const QStringList &viewNames) : QMainWindow() , mViewNames(viewNames) , mIncidenceChanger(0) , mSettings(0) , mViewPreferences(0) { mUi.setupUi(this); mUi.tabWidget->clear(); connect(mUi.addViewMenu, &QMenu::triggered, this, &MainWindow::addViewTriggered); Akonadi::ControlGui::widgetNeedsAkonadi(this); setGeometry(0, 0, 800, 600); QMetaObject::invokeMethod(this, "delayedInit", Qt::QueuedConnection); } MainWindow::~MainWindow() { delete mViewPreferences; delete mSettings; } void MainWindow::addView(const QString &viewName) { EventView *eventView = 0; const auto start = QDateTime::currentDateTime().addDays(-1); const auto end = QDateTime::currentDateTime().addDays(1); if (viewName == QLatin1String("agenda")) { eventView = new AgendaView(start.date(), end.date(), true, false, this); } else if (viewName == QLatin1String("multiagenda")) { eventView = new MultiAgendaView(this); } else if (viewName == QLatin1String("month")) { eventView = new MonthView(MonthView::Visible, this); } else if (viewName == QLatin1String("timeline")) { eventView = new TimelineView(this); } if (eventView) { eventView->setPreferences(*mViewPreferences); eventView->setCalendar(mCalendar); eventView->setIncidenceChanger(mIncidenceChanger); eventView->setDateRange(start, end); eventView->updateConfig(); mUi.tabWidget->addTab(eventView, viewName); } else { qCCritical(CALENDARVIEW_LOG) << "Cannot create view" << viewName; } } void MainWindow::delayedInit() { // create our application settings mSettings = new Settings; // create view preferences so that matching values are retrieved from // application settings mViewPreferences = new PrefsPtr(new Prefs(mSettings)); mCalendar = Akonadi::ETMCalendar::Ptr(new Akonadi::ETMCalendar()); KCheckableProxyModel *checkableProxy = mCalendar->checkableProxyModel(); QItemSelectionModel *selectionModel = checkableProxy->selectionModel(); CalendarSupport::CollectionSelection *collectionSelection = new CalendarSupport::CollectionSelection(selectionModel); EventViews::EventView::setGlobalCollectionSelection(collectionSelection); mIncidenceChanger = new IncidenceChanger(this); mCalendar->setCollectionFilteringEnabled(false); Q_FOREACH (const QString &viewName, mViewNames) { addView(viewName); } } void MainWindow::addViewTriggered(QAction *action) { QString viewName = action->text().toLower(); viewName.remove(QLatin1Char('&')); addView(viewName); } 0707010000005E000081A40000000200000002000000015F0BF3C9000006A6000000000000000000000000000000000000004900000000eventviews-VERSIONgit.20200713T074025~752bb43/src/viewerapp/mainwindow.h/* Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include "ui_mainwindow.h" #include <Akonadi/Calendar/ETMCalendar> #include <QMainWindow> namespace Akonadi { class IncidenceChanger; } namespace EventViews { class Prefs; typedef QSharedPointer<Prefs> PrefsPtr; } class QAction; class Settings; class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(const QStringList &viewNames); ~MainWindow(); private: const QStringList mViewNames; Ui_MainWindow mUi; Akonadi::ETMCalendar::Ptr mCalendar; Akonadi::IncidenceChanger *mIncidenceChanger = nullptr; Settings *mSettings = nullptr; EventViews::PrefsPtr *mViewPreferences = nullptr; private: void addView(const QString &viewName); private Q_SLOTS: void delayedInit(); void addViewTriggered(QAction *action); }; #endif 0707010000005F000081A40000000200000002000000015F0BF3C900000750000000000000000000000000000000000000004A00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/viewerapp/mainwindow.ui<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>MainWindow</class> <widget class="QMainWindow" name="MainWindow"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>523</width> <height>225</height> </rect> </property> <property name="windowTitle"> <string>CalendarViews Viewer</string> </property> <widget class="QWidget" name="centralwidget"> <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QTabWidget" name="tabWidget"> <property name="currentIndex"> <number>0</number> </property> <widget class="QWidget" name="tab"> <attribute name="title"> <string>Tab 1</string> </attribute> </widget> </widget> </item> </layout> </widget> <widget class="QMenuBar" name="menubar"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>523</width> <height>21</height> </rect> </property> <widget class="QMenu" name="addViewMenu"> <property name="title"> <string>Add View</string> </property> <addaction name="actionAgenda"/> <addaction name="actionMultiAgenda"/> <addaction name="actionMonth"/> <addaction name="actionTimeline"/> </widget> <addaction name="addViewMenu"/> </widget> <widget class="QStatusBar" name="statusbar"/> <action name="actionAgenda"> <property name="text"> <string>Agenda</string> </property> </action> <action name="actionMultiAgenda"> <property name="text"> <string>MultiAgenda</string> </property> </action> <action name="actionMonth"> <property name="text"> <string>Month</string> </property> </action> <action name="actionTimeline"> <property name="text"> <string>Timeline</string> </property> </action> </widget> <resources/> <connections/> </ui> 07070100000060000081A40000000200000002000000015F0BF3C900003D29000000000000000000000000000000000000004A00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/viewerapp/settings.kcfg<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE kcfg SYSTEM "http://www.kde.org/standards/kcfg/1.0/kcfg.dtd"> <kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" > <kcfgfile name="viewerapprc"/> <!-- PREFERENCES DIALOG --> <!-- Views Page: General Tab --> <group name="General View"> <entry type="Bool" key="Enable ToolTips"> <label>Enable tooltips for displaying summaries</label> <whatsthis>Check this box to display summary tooltips when hovering the mouse over an event or a to-do.</whatsthis> <default>true</default> </entry> </group> <!-- Views Page: Agenda View Tab --> <group name="Agenda View"> <entry type="Int" key="Hour Size"> <label>Hour size</label> <whatsthis>Select the height of the hour rows in the agenda grid, in pixels. Increasing this value will make each row in the agenda grid taller.</whatsthis> <tooltip>Set the height (in pixels) for an hour in the agenda grid</tooltip> <default>10</default> <min>4</min> <max>30</max> </entry> <entry type="Bool" key="Show Icons in Agenda View" name="EnableAgendaItemIcons"> <label>Show icons in agenda view items</label> <whatsthis>Check this box to display icons (alarm, recursion, etc.) in agenda view items.</whatsthis> <tooltip>Display icons in agenda view items</tooltip> <default>true</default> </entry> <entry type="Bool" name="ShowTodosAgendaView"> <label>Show to-dos</label> <whatsthis>Check this box to display to-dos in the agenda view.</whatsthis> <tooltip>Display to-dos in the agenda view</tooltip> <default>true</default> </entry> <entry type="Bool" key="Show current-time line" name="MarcusBainsEnabled"> <label>Show current-time (Marcus Bains) line</label> <whatsthis>Check this box to display a line in the day or week view indicating the current-time line (Marcus Bains line).</whatsthis> <tooltip>Display the current-time indicator</tooltip> <default>true</default> </entry> <entry type="Bool" key="Current-time line shows seconds" name="MarcusBainsShowSeconds"> <label>Show seconds on the current-time (Marcus Bains) line</label> <whatsthis>Check this box if you want to show seconds on the current-time line.</whatsthis> <tooltip>Display seconds with the current-time indicator</tooltip> <default>true</default> </entry> <entry type="Bool" name="SelectionStartsEditor"> <label>Time range selection in agenda view starts event editor</label> <whatsthis>Check this box to start the event editor automatically when you select a time range in the daily and weekly view. To select a time range, drag the mouse from the start time to the end time of the event you are about to plan.</whatsthis> <tooltip>Enable automatic event editor with time range selection</tooltip> <default>false</default> </entry> <entry type="Enum" key="AgendaViewColors"> <label>Color Usage</label> <choices> <choice name="CategoryInsideResourceOutside"> <label>Category inside, calendar outside</label> <whatsthis>Select the "Category inside, calendar outside" option if you would like to draw calendar items in their associated category color, with the item's border drawn in the color of its calendar. Please use the Colors and Fonts configuration page for setting these colors.</whatsthis> <tooltip>Draw agenda items in their category color inside and calendar color for their border</tooltip> </choice> <choice name="ResourceInsideCategoryOutside"> <label>Calendar inside, category outside</label> <whatsthis>Select the "Calendar inside, category outside" option if you would like to draw calendar items in their associated calendar color, with the item's border drawn in the color of its category. Please use the Colors and Fonts configuration page for setting these colors.</whatsthis> <tooltip>Draw agenda items in their calendar color inside and category color for their border</tooltip> </choice> <choice name="CategoryOnly"> <label>Only category</label> <whatsthis>Select the "Only category" option if you would like to draw calendar items (both inside and border) in the color of their associated category. Please use the Colors and Fonts configuration page for setting these colors.</whatsthis> <tooltip>Draw agenda items using their category color for the inside and border</tooltip> </choice> <choice name="ResourceOnly"> <label>Only calendar</label> <whatsthis>Select the "Only calendar" option if you would like to draw calendar items (both inside and border) in the color of their calendar. Please use the Colors and Fonts configuration page for setting these colors.</whatsthis> <tooltip>Draw agenda items using their calendar color for the inside and border</tooltip> </choice> </choices> <default>CategoryInsideResourceOutside</default> </entry> <entry type="Enum" name="AgendaViewCalendarDisplay"> <label>Multiple Calendar Display</label> <choices> <choice name="CalendarsMerged"> <label>Merge all calendars into one view</label> <whatsthis>Select the "Merge all calendars into one view" option if you would like all your calendars to be shown together in one agenda view.</whatsthis> <tooltip>Show all calendars merged together</tooltip> </choice> <choice name="CalendarsSideBySide"> <label>Show calendars side by side</label> <whatsthis>Select the "Show calendars side by side" option if you would like to see two calendars at once, in a side-by-side view.</whatsthis> <tooltip>Show two calendars side-by-side</tooltip> </choice> <choice name="AllCalendarViews"> <label>Switch between views with tabs</label> <whatsthis>Select "Switch between views with tabs" if you would like to alternate between calendars using the tab key.</whatsthis> <tooltip>Tab through calendars</tooltip> </choice> </choices> <default>CalendarsMerged</default> </entry> </group> <!-- Views Page: Month View Tab --> <group name="Month View"> <!-- <entry type="Bool" key="Enable Month-View Scrollbars" name="EnableMonthScroll"> <label>Enable scrollbars in month view cells</label> <whatsthis>Check this box to display scrollbars when clicking on a cell in the month view; they will only appear when needed though.</whatsthis> <default>false</default> </entry> --> <entry type="Bool" key="Show Icons in Month View" name="EnableMonthItemIcons"> <label>Show icons in month view items</label> <whatsthis>Check this box to display icons (alarm, recursion, etc.) in month view items.</whatsthis> <tooltip>Display icons in month view items</tooltip> <default>true</default> </entry> <entry type="Bool" name="ShowTodosMonthView"> <label>Show to-dos</label> <whatsthis>Check this box to display to-dos in the month view.</whatsthis> <tooltip>Display to-dos in the month view</tooltip> <default>true</default> </entry> <entry type="Bool" name="ShowJournalsMonthView"> <label>Show journals</label> <whatsthis>Check this box to display journals in the month view.</whatsthis> <tooltip>Display journals in the month view</tooltip> <default>true</default> </entry> <entry type="Bool" key="Full View Month"> <label>Month view uses full window</label> <whatsthis>Check this box to use the full KOrganizer window when displaying the month view. If this box is checked, you will gain some space for the monthly view, but other widgets, such as the date navigator, the item details and the calendars list, will not be displayed.</whatsthis> <default>false</default> </entry> <entry type="Enum" key="MonthViewColors"> <label>Color Usage</label> <choices> <choice name="MonthItemCategoryInsideResourceOutside"> <label>Category inside, calendar outside</label> <whatsthis>Select the "Category inside, calendar outside" option if you would like to draw calendar items in their associated category color, with the item's border drawn in the color of its calendar. Please use the Colors and Fonts configuration page for setting these colors.</whatsthis> <tooltip>Draw month items in their category color inside and calendar color for their border</tooltip> </choice> <choice name="MonthItemResourceInsideCategoryOutside"> <label>Calendar inside, category outside</label> <whatsthis>Select the "Calendar inside, category outside" option if you would like to draw calendar items in their associated calendar color, with the item's border drawn in the color of its category. Please use the Colors and Fonts configuration page for setting these colors.</whatsthis> <tooltip>Draw month items in their calendar color inside and category color for their border</tooltip> </choice> <choice name="MonthItemCategoryOnly"> <label>Only category</label> <whatsthis>Select the "Only category" option if you would like to draw calendar items (both inside and border) in the color of their associated category. Please use the Colors and Fonts configuration page for setting these colors.</whatsthis> <tooltip>Draw month items using their category color for the inside and border</tooltip> </choice> <choice name="MonthItemResourceOnly"> <label>Only calendar</label> <whatsthis>Select the "Only calendar" option if you would like to draw calendar items (both inside and border) in the color of their calendar. Please use the Colors and Fonts configuration page for setting these colors.</whatsthis> <tooltip>Draw month items using their calendar color for the inside and border</tooltip> </choice> </choices> <default>MonthItemCategoryInsideResourceOutside</default> </entry> </group> <!-- Views Page: Todo View Tab --> <group name="Todo View"> <entry type="Bool" key="Sort Completed Todos Separately"> <label>Always display completed to-dos at the bottom of the list</label> <whatsthis>Check this box if you want all completed to-dos to be always grouped at the bottom of the to-do list.</whatsthis> <default>true</default> </entry> <entry type="Bool" key="Full View Todo"> <label>To-do list view uses full window</label> <whatsthis>Check this box to use the full KOrganizer window when displaying the to-do list view. If this box is checked, you will gain some space for the to-do list view, but other widgets, such as the date navigator, the to-do details and the calendars list, will not be displayed.</whatsthis> <default>true</default> </entry> <entry type="Bool" key="Record Todos In Journals"> <label>Record completed to-dos in journal entries</label> <whatsthis>Check this box to record the completion of a to-do in a new entry of your journal automatically.</whatsthis> <default>false</default> </entry> </group> <!-- Colors and Fonts Page: Colors Tab --> <group name="Colors"> <entry type="Color" key="Holiday Color"> <label>Holiday color</label> <whatsthis>Select the holiday color here. The holiday color will be used for the holiday name in the month view and the holiday number in the date navigator.</whatsthis> <default>255, 100, 100</default> </entry> <entry type="Color" key="AgendaBackgroundColor" name="AgendaBgColor"> <label>Agenda view background color</label> <whatsthis>Select the agenda view background color here.</whatsthis> <default>255, 255, 255</default> </entry> <entry type="Color" key="Agenda MarcusBainsLine Line Color"> <label>Agenda view current-time line color</label> <whatsthis>Select a color to use for the current-time (Marcus Bains) line.</whatsthis> <tooltip>Use this color for the Agenda View current-time (Marcus Bains) line</tooltip> <default>0,0,255</default> <!-- eventviews.kcfg default is 255,0,0 --> </entry> <entry type="Color" key="WorkingHoursColor"> <label>Working hours color</label> <whatsthis>Select the working hours color for the agenda view here.</whatsthis> <default>255, 235, 154</default> </entry> <entry type="Color" key="Todo due today Color" name="TodoDueTodayColor"> <label>To-do due today color</label> <whatsthis>Select the to-do due today color here.</whatsthis> <default>255, 200, 50</default> </entry> <entry type="Color" key="Todo overdue Color" name="TodoOverdueColor"> <label>To-do overdue color</label> <whatsthis>Select the to-do overdue color here.</whatsthis> <default>255, 100, 100</default> </entry> <entry type="Color" key="Unset Category Color"> <label>"No category" color (for "Only category" drawing schemes)</label> <whatsthis>Select a color to use for the "no category" or "unset category" situation, when an item does not belong to any category. This color is used when drawing items in the agenda or month views using the "Only category" scheme.</whatsthis> <tooltip>Use this color when drawing items without a category</tooltip> <default>151, 235, 121</default> </entry> </group> <!-- Colors and Fonts Page: Fonts Tab --> <group name="Fonts"> <entry key="Agenda TimeLabels Font" type="Font"> <label>Time bar</label> <whatsthis>Press this button to configure the time bar font. The time bar is the widget that shows the hours in the agenda view. This button will open the "Select Font" dialog, allowing you to choose the hour font for the time bar.</whatsthis> </entry> <entry type="Font" key="MonthView Font"> <label>Month view</label> <whatsthis>Press this button to configure the month view font. This button will open the "Select Font" dialog, allowing you to choose the font for the items in the month view.</whatsthis> </entry> <entry type="Font" key="AgendaView Font"> <label>Agenda view</label> <whatsthis>Press this button to configure the agenda view font. This button will open the "Select Font" dialog, allowing you to choose the font for the events in the agenda view.</whatsthis> </entry> <entry key="Agenda MarcusBainsLine Font" type="Font"> <label>Current-time line</label> <whatsthis>Press this button to configure the current-time line font. This button will open the "Select Font" dialog, allowing you to choose the font for the current-time line in the agenda view.</whatsthis> </entry> </group> <!-- Group Scheduling Page --> <group name="Group Scheduling"> <entry type="Bool" key="Use Groupware Communication"> <label>Use Groupware communication</label> <whatsthis>Check this box to enable automatic generation of mails when creating, updating or deleting events (or to-dos) involving other attendees. You should check this box if you want to use the groupware functionality (e.g. Configuring Kontact as a KDE Kolab client).</whatsthis> <default>false</default> </entry> </group> </kcfg> 07070100000061000081A40000000200000002000000015F0BF3C9000000C1000000000000000000000000000000000000004B00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/viewerapp/settings.kcfgc# Code generation options for kconfig_compiler File=settings.kcfg ClassName=Settings Singleton=false Mutators=true MemberVariables=private GlobalEnums=true ItemAccessors=true SetUserTexts=true 07070100000062000041ED0000000200000002000000025F0BF3C900000000000000000000000000000000000000000000003C00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/whatsnext07070100000063000081A40000000200000002000000015F0BF3C9000033EE000000000000000000000000000000000000004E00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/whatsnext/whatsnextview.cpp/* This file is part of KOrganizer. Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "whatsnextview.h" #include <Akonadi/Calendar/ETMCalendar> #include <CalendarSupport/KCalPrefs> #include <CalendarSupport/Utils> #include <KCalUtils/IncidenceFormatter> #include <KIconLoader> #include <QBoxLayout> using namespace EventViews; void WhatsNextTextBrowser::setSource(const QUrl &name) { const QString uri = name.toString(); if (uri.startsWith(QLatin1String("event:"))) { Q_EMIT showIncidence(uri); } else if (uri.startsWith(QLatin1String("todo:"))) { Q_EMIT showIncidence(uri); } else { QTextBrowser::setSource(QUrl(uri)); } } WhatsNextView::WhatsNextView(QWidget *parent) : EventView(parent) { mView = new WhatsNextTextBrowser(this); connect(mView, &WhatsNextTextBrowser::showIncidence, this, &WhatsNextView::showIncidence); QBoxLayout *topLayout = new QVBoxLayout(this); topLayout->setContentsMargins(0, 0, 0, 0); topLayout->addWidget(mView); } WhatsNextView::~WhatsNextView() { } int WhatsNextView::currentDateCount() const { return mStartDate.daysTo(mEndDate); } void WhatsNextView::createTaskRow(KIconLoader *kil) { QString ipath; kil->loadIcon(QStringLiteral("view-calendar-tasks"), KIconLoader::NoGroup, 22, KIconLoader::DefaultState, QStringList(), &ipath); mText += QLatin1String("<h2><img src=\""); mText += ipath; mText += QLatin1String("\" width=\"22\" height=\"22\">"); mText += i18n("To-dos:") + QLatin1String("</h2>\n"); mText += QLatin1String("<ul>\n"); } void WhatsNextView::updateView() { KIconLoader *kil = KIconLoader::global(); QString ipath; kil->loadIcon(QStringLiteral("office-calendar"), KIconLoader::NoGroup, 32, KIconLoader::DefaultState, QStringList(), &ipath); mText = QStringLiteral("<table width=\"100%\">\n"); mText += QLatin1String("<tr bgcolor=\"#3679AD\"><td><h1>"); mText += QLatin1String("<img src=\""); mText += ipath; mText += QLatin1String("\" width=\"32\" height=\"32\">"); mText += QLatin1String("<font color=\"white\"> "); mText += i18n("What's Next?") + QLatin1String("</font></h1>"); mText += QLatin1String("</td></tr>\n<tr><td>"); mText += QLatin1String("<h2>"); if (mStartDate.daysTo(mEndDate) < 1) { mText += QLocale::system().toString(mStartDate); } else { mText += i18nc( "date from - to", "%1 - %2", QLocale::system().toString(mStartDate), QLocale::system().toString(mEndDate)); } mText += QLatin1String("</h2>\n"); KCalendarCore::Event::List events; events = calendar()->events(mStartDate, mEndDate, QTimeZone::systemTimeZone(), false); events = calendar()->sortEvents(events, KCalendarCore::EventSortStartDate, KCalendarCore::SortDirectionAscending); if (!events.isEmpty()) { mText += QLatin1String("<p></p>"); kil->loadIcon(QStringLiteral("view-calendar-day"), KIconLoader::NoGroup, 22, KIconLoader::DefaultState, QStringList(), &ipath); mText += QLatin1String("<h2><img src=\""); mText += ipath; mText += QLatin1String("\" width=\"22\" height=\"22\">"); mText += i18n("Events:") + QLatin1String("</h2>\n"); mText += QLatin1String("<table>\n"); for (const KCalendarCore::Event::Ptr &ev : qAsConst(events)) { if (!ev->recurs()) { appendEvent(ev); } else { KCalendarCore::Recurrence *recur = ev->recurrence(); int duration = ev->dtStart().secsTo(ev->dtEnd()); QDateTime start = recur->getPreviousDateTime(QDateTime(mStartDate, QTime(), Qt::LocalTime)); QDateTime end = start.addSecs(duration); QDateTime endDate(mEndDate, QTime(23, 59, 59), Qt::LocalTime); if (end.date() >= mStartDate) { appendEvent(ev, start.toLocalTime(), end.toLocalTime()); } const auto times = recur->timesInInterval(start, endDate); int count = times.count(); if (count > 0) { int i = 0; if (times[0] == start) { ++i; // start has already been appended } if (!times[count - 1].isValid()) { --count; // list overflow } for (; i < count && times[i].date() <= mEndDate; ++i) { appendEvent(ev, times[i].toLocalTime()); } } } } mText += QLatin1String("</table>\n"); } mTodos.clear(); KCalendarCore::Todo::List todos = calendar()->todos(KCalendarCore::TodoSortDueDate, KCalendarCore::SortDirectionAscending); if (!todos.isEmpty()) { bool taskHeaderWasCreated = false; for (const KCalendarCore::Todo::Ptr &todo : qAsConst(todos)) { if (!todo->isCompleted() && todo->hasDueDate() && todo->dtDue().date() <= mEndDate) { if (!taskHeaderWasCreated) { createTaskRow(kil); taskHeaderWasCreated = true; } appendTodo(todo); } } bool gotone = false; int priority = 1; while (!gotone && priority <= 9) { for (const KCalendarCore::Todo::Ptr &todo : qAsConst(todos)) { if (!todo->isCompleted() && (todo->priority() == priority)) { if (!taskHeaderWasCreated) { createTaskRow(kil); taskHeaderWasCreated = true; } appendTodo(todo); gotone = true; } } priority++; } if (taskHeaderWasCreated) { mText += QLatin1String("</ul>\n"); } } QStringList myEmails(CalendarSupport::KCalPrefs::instance()->allEmails()); int replies = 0; events = calendar()->events(QDate::currentDate(), QDate(2975, 12, 6), QTimeZone::systemTimeZone()); for (const KCalendarCore::Event::Ptr &ev : qAsConst(events)) { KCalendarCore::Attendee me = ev->attendeeByMails(myEmails); if (!me.isNull()) { if (me.status() == KCalendarCore::Attendee::NeedsAction && me.RSVP()) { if (replies == 0) { mText += QLatin1String("<p></p>"); kil->loadIcon(QStringLiteral("mail-reply-sender"), KIconLoader::NoGroup, 22, KIconLoader::DefaultState, QStringList(), &ipath); mText += QLatin1String("<h2><img src=\""); mText += ipath; mText += QLatin1String("\" width=\"22\" height=\"22\">"); mText += i18n("Events and to-dos that need a reply:") + QLatin1String("</h2>\n"); mText += QLatin1String("<table>\n"); } replies++; appendEvent(ev); } } } todos = calendar()->todos(); for (const KCalendarCore::Todo::Ptr &to : qAsConst(todos)) { KCalendarCore::Attendee me = to->attendeeByMails(myEmails); if (!me.isNull()) { if (me.status() == KCalendarCore::Attendee::NeedsAction && me.RSVP()) { if (replies == 0) { mText += QLatin1String("<p></p>"); kil->loadIcon(QStringLiteral("mail-reply-sender"), KIconLoader::NoGroup, 22, KIconLoader::DefaultState, QStringList(), &ipath); mText += QLatin1String("<h2><img src=\""); mText += ipath; mText += QLatin1String("\" width=\"22\" height=\"22\">"); mText += i18n("Events and to-dos that need a reply:") + QLatin1String("</h2>\n"); mText += QLatin1String("<table>\n"); } replies++; appendEvent(to); } } } if (replies > 0) { mText += QLatin1String("</table>\n"); } mText += QLatin1String("</td></tr>\n</table>\n"); mView->setText(mText); } void WhatsNextView::showDates(const QDate &start, const QDate &end, const QDate &) { mStartDate = start; mEndDate = end; updateView(); } void WhatsNextView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) { Q_UNUSED(incidenceList); Q_UNUSED(date); } void WhatsNextView::changeIncidenceDisplay(const Akonadi::Item &, Akonadi::IncidenceChanger::ChangeType) { updateView(); } void WhatsNextView::appendEvent(const KCalendarCore::Incidence::Ptr &incidence, const QDateTime &start, const QDateTime &end) { mText += QLatin1String("<tr><td><b>"); if (const KCalendarCore::Event::Ptr event = incidence.dynamicCast<KCalendarCore::Event>()) { auto starttime = start.toLocalTime(); if (!starttime.isValid()) { starttime = event->dtStart().toLocalTime(); } auto endtime = end.toLocalTime(); if (!endtime.isValid()) { endtime = starttime.addSecs(event->dtStart().secsTo(event->dtEnd())); } if (starttime.date().daysTo(endtime.date()) >= 1) { if (event->allDay()) { mText += i18nc("date from - to", "%1 - %2", QLocale().toString(starttime.date(), QLocale::ShortFormat), QLocale().toString(endtime.date(), QLocale::ShortFormat)); } else { mText += i18nc("date from - to", "%1 - %2", QLocale().toString(starttime, QLocale::ShortFormat), QLocale().toString(endtime, QLocale::ShortFormat)); } } else { if (event->allDay()) { mText += QLocale().toString(starttime.date(), QLocale::ShortFormat); } else { mText += i18nc("date, from - to", "%1, %2 - %3", QLocale().toString(starttime.date(), QLocale::ShortFormat), QLocale().toString(starttime.time(), QLocale::ShortFormat), QLocale().toString(endtime.time(), QLocale::ShortFormat)); } } } mText += QLatin1String("</b></td><td><a "); if (incidence->type() == KCalendarCore::Incidence::TypeEvent) { mText += QLatin1String("href=\"event:"); } if (incidence->type() == KCalendarCore::Incidence::TypeTodo) { mText += QLatin1String("href=\"todo:"); } mText += incidence->uid() + QLatin1String("\">"); mText += incidence->summary(); mText += QLatin1String("</a></td></tr>\n"); } void WhatsNextView::appendTodo(const KCalendarCore::Incidence::Ptr &incidence) { Akonadi::Item aitem = calendar()->item(incidence); if (mTodos.contains(aitem)) { return; } mTodos.append(aitem); mText += QLatin1String("<li><a href=\"todo:") + incidence->uid() + QLatin1String("\">"); mText += incidence->summary(); mText += QLatin1String("</a>"); if (const KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(aitem)) { if (todo->hasDueDate()) { mText += i18nc("to-do due date", " (Due: %1)", KCalUtils::IncidenceFormatter::dateTimeToString( todo->dtDue(), todo->allDay())); } mText += QLatin1String("</li>\n"); } } void WhatsNextView::showIncidence(const QString &uid) { Akonadi::Item item; Akonadi::ETMCalendar::Ptr cal = calendar(); if (!cal) { return; } if (uid.startsWith(QLatin1String("event:"))) { item = cal->item(uid.mid(6)); } else if (uid.startsWith(QLatin1String("todo:"))) { item = cal->item(uid.mid(5)); } if (item.isValid()) { Q_EMIT showIncidenceSignal(item); } } 07070100000064000081A40000000200000002000000015F0BF3C900000BE5000000000000000000000000000000000000004C00000000eventviews-VERSIONgit.20200713T074025~752bb43/src/whatsnext/whatsnextview.h/* This file is part of KOrganizer. Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef CALENDARVIEWS_WHATSNEXTVIEW_H #define CALENDARVIEWS_WHATSNEXTVIEW_H #include "eventview.h" #include <Akonadi/Calendar/IncidenceChanger> #include <KIconLoader> #include <QUrl> #include <QTextBrowser> namespace EventViews { class WhatsNextTextBrowser : public QTextBrowser { Q_OBJECT public: explicit WhatsNextTextBrowser(QWidget *parent) : QTextBrowser(parent) { } /** Reimplemented from QTextBrowser to handle links. */ void setSource(const QUrl &name) override; Q_SIGNALS: void showIncidence(const QString &uid); }; /** This class provides a view of the next events and todos */ class EVENTVIEWS_EXPORT WhatsNextView : public EventViews::EventView { Q_OBJECT public: explicit WhatsNextView(QWidget *parent = nullptr); ~WhatsNextView() override; Q_REQUIRED_RESULT int currentDateCount() const override; Q_REQUIRED_RESULT Akonadi::Item::List selectedIncidences() const override { return Akonadi::Item::List(); } Q_REQUIRED_RESULT KCalendarCore::DateList selectedIncidenceDates() const override { return KCalendarCore::DateList(); } Q_REQUIRED_RESULT bool supportsDateNavigation() const { return true; } public Q_SLOTS: void updateView() override; void showDates(const QDate &start, const QDate &end, const QDate &preferredMonth) override; void showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) override; void changeIncidenceDisplay(const Akonadi::Item &, Akonadi::IncidenceChanger::ChangeType); protected: void appendEvent(const KCalendarCore::Incidence::Ptr &, const QDateTime &start = QDateTime(), const QDateTime &end = QDateTime()); void appendTodo(const KCalendarCore::Incidence::Ptr &); private Q_SLOTS: void showIncidence(const QString &); private: void createTaskRow(KIconLoader *kil); WhatsNextTextBrowser *mView = nullptr; QString mText; QDate mStartDate; QDate mEndDate; Akonadi::Item::List mTodos; }; } #endif 07070100000065000041ED0000000200000002000000025F0BF3C900000000000000000000000000000000000000000000003400000000eventviews-VERSIONgit.20200713T074025~752bb43/tests07070100000066000081A40000000200000002000000015F0BF3C9000000F4000000000000000000000000000000000000004300000000eventviews-VERSIONgit.20200713T074025~752bb43/tests/CMakeLists.txtset(createcolorgui_test createcolorgui_test.cpp) add_executable(createcolorgui_test ${createcolorgui_test}) ecm_mark_as_test(createcolorgui_test) target_link_libraries(createcolorgui_test Qt5::Core Qt5::Gui KF5::EventViews KF5::I18n ) 07070100000067000081A40000000200000002000000015F0BF3C900000889000000000000000000000000000000000000004C00000000eventviews-VERSIONgit.20200713T074025~752bb43/tests/createcolorgui_test.cpp/* Copyright (c) 2014-2020 Laurent Montel <montel@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "createcolorgui_test.h" #include "prefs.h" #include <KLocalizedString> #include <QVBoxLayout> #include <QListWidget> #include <QApplication> #include <KAboutData> #include <QCommandLineParser> #include "calendarview_debug.h" CreateColorGui_test::CreateColorGui_test(QWidget *parent) : QWidget(parent) { QVBoxLayout *vbox = new QVBoxLayout(this); mListWidget = new QListWidget; vbox->addWidget(mListWidget); createListWidgetItem(); } CreateColorGui_test::~CreateColorGui_test() { } void CreateColorGui_test::createListWidgetItem() { EventViews::Prefs prefs; mListWidget->clear(); for (int i = 0; i < 100; ++i) { QListWidgetItem *item = new QListWidgetItem; QColor color = prefs.resourceColor(QString::number(i)); item->setBackground(color); mListWidget->addItem(item); } } int main(int argc, char **argv) { QApplication app(argc, argv); KAboutData aboutData(QStringLiteral("CreateColorGui_test"), i18n( "CreateColorGui_test"), QStringLiteral("1.0")); aboutData.setShortDescription(i18n("Test creating color")); QCommandLineParser parser; KAboutData::setApplicationData(aboutData); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); CreateColorGui_test *createColor = new CreateColorGui_test; createColor->resize(800, 600); createColor->show(); app.exec(); return 0; } 07070100000068000081A40000000200000002000000015F0BF3C900000437000000000000000000000000000000000000004A00000000eventviews-VERSIONgit.20200713T074025~752bb43/tests/createcolorgui_test.h/* Copyright (c) 2014-2020 Laurent Montel <montel@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef CREATECOLORGUI_TEST_H #define CREATECOLORGUI_TEST_H #include <QWidget> class QListWidget; class CreateColorGui_test : public QWidget { Q_OBJECT public: explicit CreateColorGui_test(QWidget *parent = nullptr); ~CreateColorGui_test(); private: void createListWidgetItem(); QListWidget *mListWidget = nullptr; }; #endif // CREATECOLORGUI_TEST_H 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!1901 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor