diff --git a/.gitignore b/.gitignore index 2bfecbb..9a0439a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .*.swp .kdev4 qtcreator-build build/* *.user *~ +buil-dir/ diff --git a/CMakeLists.txt b/CMakeLists.txt index a926a62..47f95ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,110 +1,116 @@ +# SPDX-FileCopyrightText: (C) 2008 Kelvie Wong +# SPDX-FileCopyrightText: (C) 2014 Gleb Baryshev +# SPDX-FileCopyrightText: (C) 2018 Luigi Toscano +# +# SPDX-License-Identifier: GPL-2.0-or-later + cmake_minimum_required(VERSION 2.8.12) cmake_policy(SET CMP0063 NEW) set(BASKET_DISABLE_GPG "0" CACHE BOOL "Disables GPG Support") include(FeatureSummary) find_package(X11 REQUIRED) # KDE find_package(ECM REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH}) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") IF(NOT BASKET_DISABLE_GPG) find_package(Gpgme) ENDIF(NOT BASKET_DISABLE_GPG) # libgit2 find_package(Libgit2) IF(LIBGIT2_FOUND) include_directories(${LIBGIT2_INCLUDE_DIR}) add_definitions(-DWITH_LIBGIT2) ELSE(LIBGIT2_FOUND) message("libgit2 not found, configuring without") ENDIF(LIBGIT2_FOUND) if (BUILD_KPARTS) find_package(KdepimLibs REQUIRED) include_directories(${KDEPIMLIBS_INCLUDE_DIRS}) endif (BUILD_KPARTS) find_package(Qt5 REQUIRED COMPONENTS Concurrent Core DBus Gui Widgets Xml ) include(KDEClangFormat) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDEInstallDirs) include(ECMInstallIcons) include(KDECMakeSettings) find_package(KF5 REQUIRED COMPONENTS Archive Completion Config ConfigWidgets #KCM CoreAddons Crash DBusAddons DocTools FileMetaData GlobalAccel GuiAddons I18n IconThemes KCMUtils KIO Notifications Parts Service TextWidgets WidgetsAddons WindowSystem XmlGui ) find_package(Phonon4Qt5 REQUIRED) IF(GPGME_FOUND) SET(HAVE_LIBGPGME 1) SET(LARGEFILE_SOURCE_1) ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64) ELSE(GPGME_FOUND) IF (BASKET_DISABLE_GPG) MESSAGE("GPG disabled, configuring without") ELSE (BASKET_DISABLE_GPG) MESSAGE("GPG not found, configuring without") ENDIF (BASKET_DISABLE_GPG) ENDIF(GPGME_FOUND) #TODO: find meinproc #Make libbasketcommon search for translations in basket.mo ADD_DEFINITIONS(-DTRANSLATION_DOMAIN=\"basket\") CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h ) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) add_subdirectory(doc) add_subdirectory(src) add_subdirectory(tags) add_subdirectory(welcome) add_subdirectory(backgrounds) add_subdirectory(images) add_subdirectory(file-integration) ki18n_install(po) # add clang-format target for all our real source files file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h) kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/COPYING b/COPYING deleted file mode 100644 index 5b6e7c6..0000000 --- a/COPYING +++ /dev/null @@ -1,340 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 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. - - - Copyright (C) - - 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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) year 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. - - , 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. diff --git a/LICENSES/GPL-2.0-or-later.txt b/LICENSES/GPL-2.0-or-later.txt new file mode 100644 index 0000000..1d80ac3 --- /dev/null +++ b/LICENSES/GPL-2.0-or-later.txt @@ -0,0 +1,319 @@ +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 Lesser 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. + +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. + + + +Copyright (C) + +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) year 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. + +, 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 Lesser General Public License instead +of this License. diff --git a/LICENSES/LGPL-2.0-only.txt b/LICENSES/LGPL-2.0-only.txt new file mode 100644 index 0000000..5c96471 --- /dev/null +++ b/LICENSES/LGPL-2.0-only.txt @@ -0,0 +1,446 @@ +GNU LIBRARY GENERAL PUBLIC LICENSE + +Version 2, June 1991 Copyright (C) 1991 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 library GPL. It is numbered 2 because +it goes with version 2 of the ordinary GPL.] + +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 Library General Public License, applies to some specially +designated Free Software Foundation software, and to any other libraries whose +authors decide to use it. You can use it for your libraries, 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 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 +a program 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. + +Our method of protecting your rights has two steps: (1) copyright the library, +and (2) offer you this license which gives you legal permission to copy, distribute +and/or modify the library. + +Also, for each distributor's protection, we want to make certain that everyone +understands that there is no warranty for this free library. If the library +is modified by someone else and passed on, we want its recipients to know +that what they have is not the original version, 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 companies distributing free software will individually +obtain patent licenses, thus in effect transforming the program into proprietary +software. To prevent this, we have made it clear that any patent must be licensed +for everyone's free use or not licensed at all. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License, which was designed for utility programs. This license, +the GNU Library General Public License, applies to certain designated libraries. +This license is quite different from the ordinary one; be sure to read it +in full, and don't assume that anything in it is the same as in the ordinary +license. + +The reason we have a separate public license for some libraries is that they +blur the distinction we usually make between modifying or adding to a program +and simply using it. Linking a program with a library, without changing the +library, is in some sense simply using the library, and is analogous to running +a utility program or application program. However, in a textual and legal +sense, the linked executable is a combined work, a derivative of the original +library, and the ordinary General Public License treats it as such. + +Because of this blurred distinction, using the ordinary General Public License +for libraries did not effectively promote software sharing, because most developers +did not use the libraries. We concluded that weaker conditions might promote +sharing better. + +However, unrestricted linking of non-free programs would deprive the users +of those programs of all benefit from the free status of the libraries themselves. +This Library General Public License is intended to permit developers of non-free +programs to use free libraries, while preserving your freedom as a user of +such programs to change the free libraries that are incorporated in them. +(We have not seen how to achieve this as regards changes in header files, +but we have achieved it as regards changes in the actual functions of the +Library.) The hope is that this will lead to faster development of free libraries. + +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, while the latter only works together with the library. + +Note that it is possible for a library to be covered by the ordinary General +Public License rather than by this special one. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library which contains a +notice placed by the copyright holder or other authorized party saying it +may be distributed under the terms of this Library 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 compile 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) 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. + +c) 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. + +d) 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 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. + +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 to 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 Library 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 an 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 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; 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! diff --git a/LICENSES/LGPL-2.1-or-later.txt b/LICENSES/LGPL-2.1-or-later.txt new file mode 100644 index 0000000..04bb156 --- /dev/null +++ b/LICENSES/LGPL-2.1-or-later.txt @@ -0,0 +1,468 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 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. + +[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. + +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. + + + +Copyright (C) + +This library is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation; either version 2.1 of the License, or (at your option) +any later version. + +This library is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License along +with this library; if not, write to the Free Software Foundation, Inc., 51 +Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +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! diff --git a/src/archive.cpp b/src/archive.cpp index f4729c6..e46cdbe 100644 --- a/src/archive.cpp +++ b/src/archive.cpp @@ -1,638 +1,624 @@ -/*************************************************************************** - * Copyright (C) 2006 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2006 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "archive.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //For Global::MainWindow() #include #include #include "backgroundmanager.h" #include "basketfactory.h" #include "basketlistview.h" #include "basketscene.h" #include "bnpview.h" #include "formatimporter.h" #include "global.h" #include "tag.h" #include "tools.h" #include "xmlwork.h" void Archive::save(BasketScene *basket, bool withSubBaskets, const QString &destination) { QDir dir; QProgressDialog dialog; dialog.setWindowTitle(i18n("Save as Basket Archive")); dialog.setLabelText(i18n("Saving as basket archive. Please wait...")); dialog.setCancelButton(NULL); dialog.setAutoClose(true); dialog.setRange(0, /*Preparation:*/ 1 + /*Finishing:*/ 1 + /*Basket:*/ 1 + /*SubBaskets:*/ (withSubBaskets ? Global::bnpView->basketCount(Global::bnpView->listViewItemForBasket(basket)) : 0)); dialog.setValue(0); dialog.show(); // Create the temporary folder: QString tempFolder = Global::savesFolder() + "temp-archive/"; dir.mkdir(tempFolder); // Create the temporary archive file: QString tempDestination = tempFolder + "temp-archive.tar.gz"; KTar tar(tempDestination, "application/x-gzip"); tar.open(QIODevice::WriteOnly); tar.writeDir("baskets", "", ""); dialog.setValue(dialog.value() + 1); // Preparation finished qDebug() << "Preparation finished out of " << dialog.maximum(); // Copy the baskets data into the archive: QStringList backgrounds; Archive::saveBasketToArchive(basket, withSubBaskets, &tar, backgrounds, tempFolder, &dialog); // Create a Small baskets.xml Document: QString data; QXmlStreamWriter stream(&data); XMLWork::setupXmlStream(stream, "basketTree"); Global::bnpView->saveSubHierarchy(Global::bnpView->listViewItemForBasket(basket), stream, withSubBaskets); stream.writeEndElement(); stream.writeEndDocument(); BasketScene::safelySaveToFile(tempFolder + "baskets.xml", data); tar.addLocalFile(tempFolder + "baskets.xml", "baskets/baskets.xml"); dir.remove(tempFolder + "baskets.xml"); // Save a Small tags.xml Document: QList tags; listUsedTags(basket, withSubBaskets, tags); Tag::saveTagsTo(tags, tempFolder + "tags.xml"); tar.addLocalFile(tempFolder + "tags.xml", "tags.xml"); dir.remove(tempFolder + "tags.xml"); // Save Tag Emblems (in case they are loaded on a computer that do not have those icons): QString tempIconFile = tempFolder + "icon.png"; for (Tag::List::iterator it = tags.begin(); it != tags.end(); ++it) { State::List states = (*it)->states(); for (State::List::iterator it2 = states.begin(); it2 != states.end(); ++it2) { State *state = (*it2); QPixmap icon = KIconLoader::global()->loadIcon(state->emblem(), KIconLoader::Small, 16, KIconLoader::DefaultState, QStringList(), 0L, true); if (!icon.isNull()) { icon.save(tempIconFile, "PNG"); QString iconFileName = state->emblem().replace('/', '_'); tar.addLocalFile(tempIconFile, "tag-emblems/" + iconFileName); } } } dir.remove(tempIconFile); // Finish Tar.Gz Exportation: tar.close(); // Computing the File Preview: BasketScene *previewBasket = basket; // FIXME: Use the first non-empty basket! // QPixmap previewPixmap(previewBasket->visibleWidth(), previewBasket->visibleHeight()); QPixmap previewPixmap(previewBasket->width(), previewBasket->height()); QPainter painter(&previewPixmap); // Save old state, and make the look clean ("smile, you are filmed!"): NoteSelection *selection = previewBasket->selectedNotes(); previewBasket->unselectAll(); Note *focusedNote = previewBasket->focusedNote(); previewBasket->setFocusedNote(0); previewBasket->doHoverEffects(0, Note::None); // Take the screenshot: previewBasket->render(&painter); // Go back to the old look: previewBasket->selectSelection(selection); previewBasket->setFocusedNote(focusedNote); previewBasket->doHoverEffects(); // End and save our splandid painting: painter.end(); QImage previewImage = previewPixmap.toImage(); const int PREVIEW_SIZE = 256; previewImage = previewImage.scaled(PREVIEW_SIZE, PREVIEW_SIZE, Qt::KeepAspectRatio); previewImage.save(tempFolder + "preview.png", "PNG"); // Finally Save to the Real Destination file: QFile file(destination); if (file.open(QIODevice::WriteOnly)) { ulong previewSize = QFile(tempFolder + "preview.png").size(); ulong archiveSize = QFile(tempDestination).size(); QTextStream stream(&file); stream.setCodec("ISO-8859-1"); stream << "BasKetNP:archive\n" << "version:0.6.1\n" // << "read-compatible:0.6.1\n" // << "write-compatible:0.6.1\n" << "preview*:" << previewSize << "\n"; stream.flush(); // Copy the Preview File: const unsigned long BUFFER_SIZE = 1024; char *buffer = new char[BUFFER_SIZE]; long sizeRead; QFile previewFile(tempFolder + "preview.png"); if (previewFile.open(QIODevice::ReadOnly)) { while ((sizeRead = previewFile.read(buffer, BUFFER_SIZE)) > 0) file.write(buffer, sizeRead); } stream << "archive*:" << archiveSize << "\n"; stream.flush(); // Copy the Archive File: QFile archiveFile(tempDestination); if (archiveFile.open(QIODevice::ReadOnly)) { while ((sizeRead = archiveFile.read(buffer, BUFFER_SIZE)) > 0) file.write(buffer, sizeRead); } // Clean Up: delete[] buffer; buffer = 0; file.close(); } dialog.setValue(dialog.value() + 1); // Finishing finished qDebug() << "Finishing finished"; // Clean Up Everything: dir.remove(tempFolder + "preview.png"); dir.remove(tempDestination); dir.rmdir(tempFolder); } void Archive::saveBasketToArchive(BasketScene *basket, bool recursive, KTar *tar, QStringList &backgrounds, const QString &tempFolder, QProgressDialog *progress) { // Basket need to be loaded for tags exportation. // We load it NOW so that the progress bar really reflect the state of the exportation: if (!basket->isLoaded()) { basket->load(); } QDir dir; // Save basket data: tar->addLocalDirectory(basket->fullPath(), "baskets/" + basket->folderName()); // Save basket icon: QString tempIconFile = tempFolder + "icon.png"; if (!basket->icon().isEmpty() && basket->icon() != "basket") { QPixmap icon = KIconLoader::global()->loadIcon(basket->icon(), KIconLoader::Small, 16, KIconLoader::DefaultState, QStringList(), /*path_store=*/0L, /*canReturnNull=*/true); if (!icon.isNull()) { icon.save(tempIconFile, "PNG"); QString iconFileName = basket->icon().replace('/', '_'); tar->addLocalFile(tempIconFile, "basket-icons/" + iconFileName); } } // Save basket background image: QString imageName = basket->backgroundImageName(); if (!basket->backgroundImageName().isEmpty() && !backgrounds.contains(imageName)) { QString backgroundPath = Global::backgroundManager->pathForImageName(imageName); if (!backgroundPath.isEmpty()) { // Save the background image: tar->addLocalFile(backgroundPath, "backgrounds/" + imageName); // Save the preview image: QString previewPath = Global::backgroundManager->previewPathForImageName(imageName); if (!previewPath.isEmpty()) tar->addLocalFile(previewPath, "backgrounds/previews/" + imageName); // Save the configuration file: QString configPath = backgroundPath + ".config"; if (dir.exists(configPath)) tar->addLocalFile(configPath, "backgrounds/" + imageName + ".config"); } backgrounds.append(imageName); } progress->setValue(progress->value() + 1); // Basket exportation finished qDebug() << basket->basketName() << " finished"; // Recursively save child baskets: BasketListViewItem *item = Global::bnpView->listViewItemForBasket(basket); if (recursive) { for (int i = 0; i < item->childCount(); i++) { saveBasketToArchive(((BasketListViewItem *)item->child(i))->basket(), recursive, tar, backgrounds, tempFolder, progress); } } } void Archive::listUsedTags(BasketScene *basket, bool recursive, QList &list) { basket->listUsedTags(list); BasketListViewItem *item = Global::bnpView->listViewItemForBasket(basket); if (recursive) { for (int i = 0; i < item->childCount(); i++) { listUsedTags(((BasketListViewItem *)item->child(i))->basket(), recursive, list); } } } void Archive::open(const QString &path) { // Create the temporary folder: QString tempFolder = Global::savesFolder() + "temp-archive/"; QDir dir; dir.mkdir(tempFolder); const qint64 BUFFER_SIZE = 1024; QFile file(path); if (file.open(QIODevice::ReadOnly)) { QTextStream stream(&file); stream.setCodec("ISO-8859-1"); QString line = stream.readLine(); if (line != "BasKetNP:archive") { KMessageBox::error(0, i18n("This file is not a basket archive."), i18n("Basket Archive Error")); file.close(); Tools::deleteRecursively(tempFolder); return; } QString version; QStringList readCompatibleVersions; QStringList writeCompatibleVersions; while (!stream.atEnd()) { // Get Key/Value Pair From the Line to Read: line = stream.readLine(); int index = line.indexOf(':'); QString key; QString value; if (index >= 0) { key = line.left(index); value = line.right(line.length() - index - 1); } else { key = line; value = ""; } if (key == "version") { version = value; } else if (key == "read-compatible") { readCompatibleVersions = value.split(';'); } else if (key == "write-compatible") { writeCompatibleVersions = value.split(';'); } else if (key == "preview*") { bool ok; qint64 size = value.toULong(&ok); if (!ok) { KMessageBox::error(0, i18n("This file is corrupted. It can not be opened."), i18n("Basket Archive Error")); file.close(); Tools::deleteRecursively(tempFolder); return; } // Get the preview file: // FIXME: We do not need the preview for now // QFile previewFile(tempFolder + "preview.png"); // if (previewFile.open(QIODevice::WriteOnly)) { stream.seek(stream.pos() + size); } else if (key == "archive*") { if (version != "0.6.1" && readCompatibleVersions.contains("0.6.1") && !writeCompatibleVersions.contains("0.6.1")) { KMessageBox::information(0, i18n("This file was created with a recent version of %1. " "It can be opened but not every information will be available to you. " "For instance, some notes may be missing because they are of a type only available in new versions. " "When saving the file back, consider to save it to another file, to preserve the original one.", QGuiApplication::applicationDisplayName()), i18n("Basket Archive Error")); } if (version != "0.6.1" && !readCompatibleVersions.contains("0.6.1") && !writeCompatibleVersions.contains("0.6.1")) { KMessageBox::error(0, i18n("This file was created with a recent version of %1. Please upgrade to a newer version to be able to open that file.", QGuiApplication::applicationDisplayName()), i18n("Basket Archive Error")); file.close(); Tools::deleteRecursively(tempFolder); return; } bool ok; qint64 size = value.toULong(&ok); if (!ok) { KMessageBox::error(0, i18n("This file is corrupted. It can not be opened."), i18n("Basket Archive Error")); file.close(); Tools::deleteRecursively(tempFolder); return; } if (Global::activeMainWindow()) { Global::activeMainWindow()->raise(); } // Get the archive file: QString tempArchive = tempFolder + "temp-archive.tar.gz"; QFile archiveFile(tempArchive); file.seek(stream.pos()); if (archiveFile.open(QIODevice::WriteOnly)) { char *buffer = new char[BUFFER_SIZE]; qint64 sizeRead; while ((sizeRead = file.read(buffer, qMin(BUFFER_SIZE, size))) > 0) { archiveFile.write(buffer, sizeRead); size -= sizeRead; } archiveFile.close(); delete[] buffer; // Extract the Archive: QString extractionFolder = tempFolder + "extraction/"; QDir dir; dir.mkdir(extractionFolder); KTar tar(tempArchive, "application/x-gzip"); tar.open(QIODevice::ReadOnly); tar.directory()->copyTo(extractionFolder); tar.close(); // Import the Tags: importTagEmblems(extractionFolder); // Import and rename tag emblems BEFORE loading them! QMap mergedStates = Tag::loadTags(extractionFolder + "tags.xml"); if (mergedStates.count() > 0) { Tag::saveTags(); } // Import the Background Images: importArchivedBackgroundImages(extractionFolder); // Import the Baskets: renameBasketFolders(extractionFolder, mergedStates); stream.seek(file.pos()); } } else if (key.endsWith('*')) { // We do not know what it is, but we should read the embedded-file in order to discard it: bool ok; qint64 size = value.toULong(&ok); if (!ok) { KMessageBox::error(0, i18n("This file is corrupted. It can not be opened."), i18n("Basket Archive Error")); file.close(); Tools::deleteRecursively(tempFolder); return; } // Get the archive file: char *buffer = new char[BUFFER_SIZE]; qint64 sizeRead; while ((sizeRead = file.read(buffer, qMin(BUFFER_SIZE, size))) > 0) { size -= sizeRead; } delete[] buffer; } else { // We do not know what it is, and we do not care. } // Analyze the Value, if Understood: } file.close(); } Tools::deleteRecursively(tempFolder); } /** * When opening a basket archive that come from another computer, * it can contains tags that use icons (emblems) that are not present on that computer. * Fortunately, basket archives contains a copy of every used icons. * This method check for every emblems and import the missing ones. * It also modify the tags.xml copy for the emblems to point to the absolute path of the imported icons. */ void Archive::importTagEmblems(const QString &extractionFolder) { QDomDocument *document = XMLWork::openFile("basketTags", extractionFolder + "tags.xml"); if (document == 0) return; QDomElement docElem = document->documentElement(); QDir dir; dir.mkdir(Global::savesFolder() + "tag-emblems/"); FormatImporter copier; // Only used to copy files synchronously QDomNode node = docElem.firstChild(); while (!node.isNull()) { QDomElement element = node.toElement(); if ((!element.isNull()) && element.tagName() == "tag") { QDomNode subNode = element.firstChild(); while (!subNode.isNull()) { QDomElement subElement = subNode.toElement(); if ((!subElement.isNull()) && subElement.tagName() == "state") { QString emblemName = XMLWork::getElementText(subElement, "emblem"); if (!emblemName.isEmpty()) { QPixmap emblem = KIconLoader::global()->loadIcon(emblemName, KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, /*canReturnNull=*/true); // The icon does not exists on that computer, import it: if (emblem.isNull()) { // Of the emblem path was eg. "/home/seb/emblem.png", it was exported as "tag-emblems/_home_seb_emblem.png". // So we need to copy that image to "~/.local/share/basket/tag-emblems/emblem.png": int slashIndex = emblemName.lastIndexOf('/'); QString emblemFileName = (slashIndex < 0 ? emblemName : emblemName.right(slashIndex - 2)); QString source = extractionFolder + "tag-emblems/" + emblemName.replace('/', '_'); QString destination = Global::savesFolder() + "tag-emblems/" + emblemFileName; if (!dir.exists(destination) && dir.exists(source)) copier.copyFolder(source, destination); // Replace the emblem path in the tags.xml copy: QDomElement emblemElement = XMLWork::getElement(subElement, "emblem"); subElement.removeChild(emblemElement); XMLWork::addElement(*document, subElement, "emblem", destination); } } } subNode = subNode.nextSibling(); } } node = node.nextSibling(); } BasketScene::safelySaveToFile(extractionFolder + "tags.xml", document->toString()); } void Archive::importArchivedBackgroundImages(const QString &extractionFolder) { FormatImporter copier; // Only used to copy files synchronously QString destFolder = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/basket/backgrounds/"; QDir().mkpath(destFolder); // does not exist at the first run when addWelcomeBaskets is called QDir dir(extractionFolder + "backgrounds/", /*nameFilder=*/"*.png", /*sortSpec=*/QDir::Name | QDir::IgnoreCase, /*filterSpec=*/QDir::Files | QDir::NoSymLinks); QStringList files = dir.entryList(); for (QStringList::Iterator it = files.begin(); it != files.end(); ++it) { QString image = *it; if (!Global::backgroundManager->exists(image)) { // Copy images: QString imageSource = extractionFolder + "backgrounds/" + image; QString imageDest = destFolder + image; copier.copyFolder(imageSource, imageDest); // Copy configuration file: QString configSource = extractionFolder + "backgrounds/" + image + ".config"; QString configDest = destFolder + image; if (dir.exists(configSource)) copier.copyFolder(configSource, configDest); // Copy preview: QString previewSource = extractionFolder + "backgrounds/previews/" + image; QString previewDest = destFolder + "previews/" + image; if (dir.exists(previewSource)) { dir.mkdir(destFolder + "previews/"); // Make sure the folder exists! copier.copyFolder(previewSource, previewDest); } // Append image to database: Global::backgroundManager->addImage(imageDest); } } } void Archive::renameBasketFolders(const QString &extractionFolder, QMap &mergedStates) { QDomDocument *doc = XMLWork::openFile("basketTree", extractionFolder + "baskets/baskets.xml"); if (doc != 0) { QMap folderMap; QDomElement docElem = doc->documentElement(); QDomNode node = docElem.firstChild(); renameBasketFolder(extractionFolder, node, folderMap, mergedStates); loadExtractedBaskets(extractionFolder, node, folderMap, 0); } } void Archive::renameBasketFolder(const QString &extractionFolder, QDomNode &basketNode, QMap &folderMap, QMap &mergedStates) { QDomNode n = basketNode; while (!n.isNull()) { QDomElement element = n.toElement(); if ((!element.isNull()) && element.tagName() == "basket") { QString folderName = element.attribute("folderName"); if (!folderName.isEmpty()) { // Find a folder name: QString newFolderName = BasketFactory::newFolderName(); folderMap[folderName] = newFolderName; // Reserve the folder name: QDir dir; dir.mkdir(Global::basketsFolder() + newFolderName); // Rename the merged tag ids: // if (mergedStates.count() > 0) { renameMergedStatesAndBasketIcon(extractionFolder + "baskets/" + folderName + ".basket", mergedStates, extractionFolder); // } // Child baskets: QDomNode node = element.firstChild(); renameBasketFolder(extractionFolder, node, folderMap, mergedStates); } } n = n.nextSibling(); } } void Archive::renameMergedStatesAndBasketIcon(const QString &fullPath, QMap &mergedStates, const QString &extractionFolder) { QDomDocument *doc = XMLWork::openFile("basket", fullPath); if (doc == 0) return; QDomElement docElem = doc->documentElement(); QDomElement properties = XMLWork::getElement(docElem, "properties"); importBasketIcon(properties, extractionFolder); QDomElement notes = XMLWork::getElement(docElem, "notes"); if (mergedStates.count() > 0) renameMergedStates(notes, mergedStates); BasketScene::safelySaveToFile(fullPath, /*"\n" + */ doc->toString()); } void Archive::importBasketIcon(QDomElement properties, const QString &extractionFolder) { QString iconName = XMLWork::getElementText(properties, "icon"); if (!iconName.isEmpty() && iconName != "basket") { QPixmap icon = KIconLoader::global()->loadIcon(iconName, KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, /*canReturnNull=*/true); // The icon does not exists on that computer, import it: if (icon.isNull()) { QDir dir; dir.mkdir(Global::savesFolder() + "basket-icons/"); FormatImporter copier; // Only used to copy files synchronously // Of the icon path was eg. "/home/seb/icon.png", it was exported as "basket-icons/_home_seb_icon.png". // So we need to copy that image to "~/.local/share/basket/basket-icons/icon.png": int slashIndex = iconName.lastIndexOf('/'); QString iconFileName = (slashIndex < 0 ? iconName : iconName.right(slashIndex - 2)); QString source = extractionFolder + "basket-icons/" + iconName.replace('/', '_'); QString destination = Global::savesFolder() + "basket-icons/" + iconFileName; if (!dir.exists(destination)) copier.copyFolder(source, destination); // Replace the emblem path in the tags.xml copy: QDomElement iconElement = XMLWork::getElement(properties, "icon"); properties.removeChild(iconElement); QDomDocument document = properties.ownerDocument(); XMLWork::addElement(document, properties, "icon", destination); } } } void Archive::renameMergedStates(QDomNode notes, QMap &mergedStates) { QDomNode n = notes.firstChild(); while (!n.isNull()) { QDomElement element = n.toElement(); if (!element.isNull()) { if (element.tagName() == "group") { renameMergedStates(n, mergedStates); } else if (element.tagName() == "note") { QString tags = XMLWork::getElementText(element, "tags"); if (!tags.isEmpty()) { QStringList tagNames = tags.split(';'); for (QStringList::Iterator it = tagNames.begin(); it != tagNames.end(); ++it) { QString &tag = *it; if (mergedStates.contains(tag)) { tag = mergedStates[tag]; } } QString newTags = tagNames.join(";"); QDomElement tagsElement = XMLWork::getElement(element, "tags"); element.removeChild(tagsElement); QDomDocument document = element.ownerDocument(); XMLWork::addElement(document, element, "tags", newTags); } } } n = n.nextSibling(); } } void Archive::loadExtractedBaskets(const QString &extractionFolder, QDomNode &basketNode, QMap &folderMap, BasketScene *parent) { bool basketSetAsCurrent = (parent != 0); QDomNode n = basketNode; while (!n.isNull()) { QDomElement element = n.toElement(); if ((!element.isNull()) && element.tagName() == "basket") { QString folderName = element.attribute("folderName"); if (!folderName.isEmpty()) { // Move the basket folder to its destination, while renaming it uniquely: QString newFolderName = folderMap[folderName]; FormatImporter copier; // The folder has been "reserved" by creating it. Avoid asking the user to override: QDir dir; dir.rmdir(Global::basketsFolder() + newFolderName); copier.moveFolder(extractionFolder + "baskets/" + folderName, Global::basketsFolder() + newFolderName); // Append and load the basket in the tree: BasketScene *basket = Global::bnpView->loadBasket(newFolderName); BasketListViewItem *basketItem = Global::bnpView->appendBasket(basket, (basket && parent ? Global::bnpView->listViewItemForBasket(parent) : 0)); basketItem->setExpanded(!XMLWork::trueOrFalse(element.attribute("folded", "false"), false)); QDomElement properties = XMLWork::getElement(element, "properties"); importBasketIcon(properties, extractionFolder); // Rename the icon fileName if necessary basket->loadProperties(properties); // Open the first basket of the archive: if (!basketSetAsCurrent) { Global::bnpView->setCurrentBasket(basket); basketSetAsCurrent = true; } QDomNode node = element.firstChild(); loadExtractedBaskets(extractionFolder, node, folderMap, basket); } } n = n.nextSibling(); } } diff --git a/src/archive.h b/src/archive.h index 951c41a..ea1d50d 100644 --- a/src/archive.h +++ b/src/archive.h @@ -1,62 +1,48 @@ -/*************************************************************************** - * Copyright (C) 2006 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2006 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef ARCHIVE_H #define ARCHIVE_H #include #include class BasketScene; class Tag; class QString; class QStringList; class QDomNode; class QProgressDialog; class QDomElement; class KTar; class KProgress; /** * @author Sébastien Laoût */ class Archive { public: static void save(BasketScene *basket, bool withSubBaskets, const QString &destination); static void open(const QString &path); private: // Convenient Methods for Saving: static void saveBasketToArchive(BasketScene *basket, bool recursive, KTar *tar, QStringList &backgrounds, const QString &tempFolder, QProgressDialog *progress); static void listUsedTags(BasketScene *basket, bool recursive, QList &list); // Convenient Methods for Loading: static void renameBasketFolders(const QString &extractionFolder, QMap &mergedStates); static void renameBasketFolder(const QString &extractionFolder, QDomNode &basketNode, QMap &folderMap, QMap &mergedStates); static void renameMergedStatesAndBasketIcon(const QString &fullPath, QMap &mergedStates, const QString &extractionFolder); static void renameMergedStates(QDomNode notes, QMap &mergedStates); static void importBasketIcon(QDomElement properties, const QString &extractionFolder); static void loadExtractedBaskets(const QString &extractionFolder, QDomNode &basketNode, QMap &folderMap, BasketScene *parent); static void importTagEmblems(const QString &extractionFolder); static void importArchivedBackgroundImages(const QString &extractionFolder); }; #endif diff --git a/src/backgroundmanager.cpp b/src/backgroundmanager.cpp index ecd38d6..e1737b3 100644 --- a/src/backgroundmanager.cpp +++ b/src/backgroundmanager.cpp @@ -1,394 +1,380 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "backgroundmanager.h" #include #include #include #include #include #include #include #include /** class BackgroundEntry: */ BackgroundEntry::BackgroundEntry(const QString &location) { this->location = location; name = QUrl::fromLocalFile(location).fileName(); tiled = false; pixmap = 0; preview = 0; customersCount = 0; } BackgroundEntry::~BackgroundEntry() { delete pixmap; delete preview; } /** class OpaqueBackgroundEntry: */ OpaqueBackgroundEntry::OpaqueBackgroundEntry(const QString &name, const QColor &color) { this->name = name; this->color = color; pixmap = 0; customersCount = 0; } OpaqueBackgroundEntry::~OpaqueBackgroundEntry() { delete pixmap; } /** class BackgroundManager: */ BackgroundManager::BackgroundManager() { /// qDebug() << "BackgroundManager: Found the following background images in "; QStringList directories = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation) /* WARNING: no more trailing slashes */; // eg. { "/home/seb/.kde/share/apps/", "/usr/share/apps/" } // For each folder: for (QStringList::Iterator it = directories.begin(); it != directories.end(); ++it) { // For each file in those directories: QDir dir(*it + "basket/backgrounds/", /*nameFilder=*/"*.png", /*sortSpec=*/QDir::Name | QDir::IgnoreCase, /*filterSpec=*/QDir::Files | QDir::NoSymLinks); /// qDebug() << *it + "basket/backgrounds/ "; QStringList files = dir.entryList(); for (QStringList::Iterator it2 = files.begin(); it2 != files.end(); ++it2) // TODO: If an image name is present in two folders? addImage(*it + "basket/backgrounds/" + *it2); } /// qDebug() << ":"; /// for (BackgroundsList::Iterator it = m_backgroundsList.begin(); it != m_backgroundsList.end(); ++it) /// qDebug() << "* " << (*it)->location << " [ref: " << (*it)->name << "]"; connect(&m_garbageTimer, SIGNAL(timeout()), this, SLOT(doGarbage())); } BackgroundManager::~BackgroundManager() { qDeleteAll(m_backgroundsList); qDeleteAll(m_opaqueBackgroundsList); } void BackgroundManager::addImage(const QString &fullPath) { m_backgroundsList.append(new BackgroundEntry(fullPath)); } BackgroundEntry *BackgroundManager::backgroundEntryFor(const QString &image) { for (BackgroundsList::Iterator it = m_backgroundsList.begin(); it != m_backgroundsList.end(); ++it) if ((*it)->name == image) return *it; return 0; } OpaqueBackgroundEntry *BackgroundManager::opaqueBackgroundEntryFor(const QString &image, const QColor &color) { for (OpaqueBackgroundsList::Iterator it = m_opaqueBackgroundsList.begin(); it != m_opaqueBackgroundsList.end(); ++it) if ((*it)->name == image && (*it)->color == color) return *it; return 0; } bool BackgroundManager::subscribe(const QString &image) { BackgroundEntry *entry = backgroundEntryFor(image); if (entry) { // If it's the first time something subscribe to this image: if (!entry->pixmap) { // Try to load the pixmap: entry->pixmap = new QPixmap(entry->location); // Try to figure out if it's a tiled background image or not (default to NO): KConfig config(entry->location + ".config", KConfig::SimpleConfig); KConfigGroup configGroup = config.group("BasKet Background Image Configuration"); entry->tiled = configGroup.readEntry("tiled", false); } // Return if the image loading has failed: if (entry->pixmap->isNull()) { /// qDebug() << "BackgroundManager: Failed to load " << entry->location; return false; } // Success: effectively subscribe: ++entry->customersCount; return true; } else { // Don't exist: subscription failed: /// qDebug() << "BackgroundManager: Requested unexisting image: " << image; return false; } } bool BackgroundManager::subscribe(const QString &image, const QColor &color) { BackgroundEntry *backgroundEntry = backgroundEntryFor(image); // First, if the image doesn't exist, isn't subscribed, or failed to load then we don't go further: if (!backgroundEntry || !backgroundEntry->pixmap || backgroundEntry->pixmap->isNull()) { /// qDebug() << "BackgroundManager: Requested an unexisting or unsubscribed image: (" << image << "," << color.name() << ")..."; return false; } OpaqueBackgroundEntry *opaqueBackgroundEntry = opaqueBackgroundEntryFor(image, color); // If this couple is requested for the first time or it haven't been subscribed for a long time enough, create it: if (!opaqueBackgroundEntry) { /// qDebug() << "BackgroundManager: Computing (" << image << "," << color.name() << ")..."; opaqueBackgroundEntry = new OpaqueBackgroundEntry(image, color); opaqueBackgroundEntry->pixmap = new QPixmap(backgroundEntry->pixmap->size()); opaqueBackgroundEntry->pixmap->fill(color); QPainter painter(opaqueBackgroundEntry->pixmap); painter.drawPixmap(0, 0, *(backgroundEntry->pixmap)); painter.end(); m_opaqueBackgroundsList.append(opaqueBackgroundEntry); } // We are now sure the entry exist, do the subscription: ++opaqueBackgroundEntry->customersCount; return true; } void BackgroundManager::unsubscribe(const QString &image) { BackgroundEntry *entry = backgroundEntryFor(image); if (!entry) { /// qDebug() << "BackgroundManager: Wanted to unsubscribe a not subscribed image: " << image; return; } --entry->customersCount; if (entry->customersCount <= 0) requestDelayedGarbage(); } void BackgroundManager::unsubscribe(const QString &image, const QColor &color) { OpaqueBackgroundEntry *entry = opaqueBackgroundEntryFor(image, color); if (!entry) { /// qDebug() << "BackgroundManager: Wanted to unsubscribe a not subscribed colored image: (" << image << "," << color.name() << ")"; return; } --entry->customersCount; if (entry->customersCount <= 0) requestDelayedGarbage(); } QPixmap *BackgroundManager::pixmap(const QString &image) { BackgroundEntry *entry = backgroundEntryFor(image); if (!entry || !entry->pixmap || entry->pixmap->isNull()) { /// qDebug() << "BackgroundManager: Requested an unexisting or unsubscribed image: " << image; return 0; } return entry->pixmap; } QPixmap *BackgroundManager::opaquePixmap(const QString &image, const QColor &color) { OpaqueBackgroundEntry *entry = opaqueBackgroundEntryFor(image, color); if (!entry || !entry->pixmap || entry->pixmap->isNull()) { /// qDebug() << "BackgroundManager: Requested an unexisting or unsubscribed colored image: (" << image << "," << color.name() << ")"; return 0; } return entry->pixmap; } bool BackgroundManager::tiled(const QString &image) { BackgroundEntry *entry = backgroundEntryFor(image); if (!entry || !entry->pixmap || entry->pixmap->isNull()) { /// qDebug() << "BackgroundManager: Requested an unexisting or unsubscribed image: " << image; return false; } return entry->tiled; } bool BackgroundManager::exists(const QString &image) { for (BackgroundsList::iterator it = m_backgroundsList.begin(); it != m_backgroundsList.end(); ++it) if ((*it)->name == image) return true; return false; } QStringList BackgroundManager::imageNames() { QStringList list; for (BackgroundsList::iterator it = m_backgroundsList.begin(); it != m_backgroundsList.end(); ++it) list.append((*it)->name); return list; } QPixmap *BackgroundManager::preview(const QString &image) { static const int MAX_WIDTH = 100; static const int MAX_HEIGHT = 75; static const QColor PREVIEW_BG = Qt::white; BackgroundEntry *entry = backgroundEntryFor(image); if (!entry) { /// qDebug() << "BackgroundManager: Requested the preview of an unexisting image: " << image; return 0; } // The easiest way: already computed: if (entry->preview) return entry->preview; // Then, try to load the preview from file: QString previewPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "basket/backgrounds/previews/" + entry->name); QPixmap *previewPixmap = new QPixmap(previewPath); // Success: if (!previewPixmap->isNull()) { /// qDebug() << "BackgroundManager: Loaded image preview for " << entry->location << " from file " << previewPath; entry->preview = previewPixmap; return entry->preview; } // We failed? Then construct it: // Note: if a preview is requested, it's because the user is currently choosing an image. // Since we need that image to create the preview, we keep the image in memory. // Then, it will already be loaded when user press [OK] in the background image chooser. // BUT we also delay a garbage because we don't want EVERY images to be loaded if the user use only a few of them, of course: // Already used? Good: we don't have to load it... if (!entry->pixmap) { // Note: it's a code duplication from BackgroundManager::subscribe(const QString &image), // Because, as we are loading the pixmap we ALSO need to know if it's a tile or not, in case that image will soon be used (and not destroyed by the garbager): entry->pixmap = new QPixmap(entry->location); // Try to figure out if it's a tiled background image or not (default to NO): KConfig config(entry->location + ".config"); KConfigGroup configGroup = config.group("BasKet Background Image Configuration"); entry->tiled = configGroup.readEntry("tiled", false); } // The image cannot be loaded, we failed: if (entry->pixmap->isNull()) return 0; // Good that we are still alive: entry->pixmap contains the pixmap to rescale down for the preview: // Compute new size: int width = entry->pixmap->width(); int height = entry->pixmap->height(); if (width > MAX_WIDTH) { height = height * MAX_WIDTH / width; width = MAX_WIDTH; } if (height > MAX_HEIGHT) { width = width * MAX_HEIGHT / height; height = MAX_HEIGHT; } // And create the resulting pixmap: QPixmap *result = new QPixmap(width, height); result->fill(PREVIEW_BG); QImage imageToScale = entry->pixmap->toImage(); QPixmap pmScaled = QPixmap::fromImage(imageToScale.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); QPainter painter(result); painter.drawPixmap(0, 0, pmScaled); painter.end(); // Saving it to file for later: QString folder = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/basket/backgrounds/previews/"; result->save(folder + entry->name, "PNG"); // Ouf! That's done: entry->preview = result; requestDelayedGarbage(); return entry->preview; } QString BackgroundManager::pathForImageName(const QString &image) { BackgroundEntry *entry = backgroundEntryFor(image); if (entry == 0) return ""; else return entry->location; } QString BackgroundManager::previewPathForImageName(const QString &image) { BackgroundEntry *entry = backgroundEntryFor(image); if (entry == 0) return ""; else { QString previewPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "basket/backgrounds/previews/" + entry->name); QDir dir; if (!dir.exists(previewPath)) return ""; else return previewPath; } } void BackgroundManager::requestDelayedGarbage() { static const int DELAY = 60 /*seconds*/; if (!m_garbageTimer.isActive()) { m_garbageTimer.setSingleShot(true); m_garbageTimer.start(DELAY * 1000 /*ms*/); } } void BackgroundManager::doGarbage() { /// qDebug() << "BackgroundManager: Doing garbage..."; /// qDebug() << "BackgroundManager: Images:"; for (BackgroundsList::Iterator it = m_backgroundsList.begin(); it != m_backgroundsList.end(); ++it) { BackgroundEntry *entry = *it; /// qDebug() << "* " << entry->name << ": used " << entry->customersCount << " times"; if (entry->customersCount <= 0 && entry->pixmap) { /// qDebug() << " [Deleted cached pixmap]"; delete entry->pixmap; entry->pixmap = 0; } /// qDebug(); } /// qDebug() << "BackgroundManager: Opaque Cached Images:"; for (OpaqueBackgroundsList::Iterator it = m_opaqueBackgroundsList.begin(); it != m_opaqueBackgroundsList.end();) { OpaqueBackgroundEntry *entry = *it; /// qDebug() << "* " << entry->name << "," << entry->color.name() << ": used " << entry->customersCount << " times"; if (entry->customersCount <= 0) { /// qDebug() << " [Deleted entry]"; delete entry->pixmap; entry->pixmap = 0; it = m_opaqueBackgroundsList.erase(it); } else ++it; /// qDebug(); } } diff --git a/src/backgroundmanager.h b/src/backgroundmanager.h index b4d4a60..adf13e7 100644 --- a/src/backgroundmanager.h +++ b/src/backgroundmanager.h @@ -1,136 +1,122 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef BACKGROUNDMANAGER_H #define BACKGROUNDMANAGER_H #include #include #include #include class QPixmap; class QString; /** A node in the list of background images of BackgroundManager. * It can only be used by BackgroundManager because it is an internal structure of this manager. * @author Sébastien Laoût */ class BackgroundEntry { friend class BackgroundManager; public: ~BackgroundEntry(); protected: BackgroundEntry(const QString &location); QString name; QString location; bool tiled; /// << Only valid after some object subscribed to this image! Because it's only read at this time. QPixmap *pixmap; /// << Only valid (non-null) after some object subscribed to this image! Because it's only read at this time. QPixmap *preview; /// << Only valid (non-null) after some object requested the preview. int customersCount; }; /** A node in the list of opaque background images (with a background color applied to an image) of BackgroundManager. * It can only be used by BackgroundManager because it is an internal structure of this manager. * @author Sébastien Laoût */ class OpaqueBackgroundEntry { friend class BackgroundManager; public: ~OpaqueBackgroundEntry(); protected: OpaqueBackgroundEntry(const QString &name, const QColor &color); QString name; QColor color; QPixmap *pixmap; int customersCount; }; /** Manage the list of background images. * BASIC FUNCTIONNING OF A BACKGROUND CHOOSER: * It get all image names with imageNames() to put them in eg. a QComboBox and then, * when it's time to get the preview of an image it call preview() with the image name to get it. * Preview are only computed on demand and then cached to fast the next demands (only the pointer will have to be returned). * Previews are scaled to fit in a rectangle of 100 by 75 pixels, and with a white background color. * They are also saved to files, so that the scaling/opaquification has not to be done later (they will be directly loaded from file). * Previews are saved in Global::backgroundsFolder()+"previews/", so that emptying the folder is sufficient to remove them. * BASIC FUNCTIONING OF AN IMAGE REQUESTER: * When eg. a basket is assigned an image name, it register it with subscribe(). * The full pixmap is then loaded from file and cached (if it was not already loaded) and the "tiled" property is read from the image configuration file. * If this object want to have the pixmap applied on a background color (for no transparency => really faster drawing), * it should register for the couple (imageName,color) with subscribe(): the pixmap will be created in the cache. * Then, the object can get the subscribed images with pixmap() or opaquePixmap() and know if it's tiled with tiled(). * When the user removed the object background image (or when the object/basket/... is removed), the object should call unsubscribe() for * EVERY subscribed image and image couples. Usage count is decreased for those images and a garbage collector will remove the cached images * if nothing is subscribed to them (to free memory). * @author Sébastien Laoût */ class BackgroundManager : private QObject { Q_OBJECT private: /// LIST OF IMAGES: typedef QList BackgroundsList; typedef QList OpaqueBackgroundsList; public: /// CONTRUCTOR AND DESTRUCTOR: BackgroundManager(); ~BackgroundManager() override; /// SUBSCRIPTION TO IMAGES: bool subscribe(const QString &image); /// << @Return true if the loading is a success. In the counter-case, calling methods below is unsafe with this @p image name. bool subscribe(const QString &image, const QColor &color); /// << Idem. void unsubscribe(const QString &image); void unsubscribe(const QString &image, const QColor &color); /// GETTING THE IMAGES AND PROPERTIES: QPixmap *pixmap(const QString &image); QPixmap *opaquePixmap(const QString &image, const QColor &color); bool tiled(const QString &image); /// LIST OF IMAGES AND PREVIEWS: bool exists(const QString &image); QStringList imageNames(); QPixmap *preview(const QString &image); /// USED FOR EXPORTATION: QString pathForImageName(const QString &image); /// << It is STRONGLY advised to not use those two methods unless it's to copy (export) the images or something like that... QString previewPathForImageName(const QString &image); /// USED FOR IMPORTATION: void addImage(const QString &fullPath); private: BackgroundEntry *backgroundEntryFor(const QString &image); OpaqueBackgroundEntry *opaqueBackgroundEntryFor(const QString &image, const QColor &color); private: BackgroundsList m_backgroundsList; OpaqueBackgroundsList m_opaqueBackgroundsList; QTimer m_garbageTimer; private slots: void requestDelayedGarbage(); void doGarbage(); }; #endif // BACKGROUNDMANAGER_H diff --git a/src/backup.cpp b/src/backup.cpp index 17bfec4..22fd2e9 100644 --- a/src/backup.cpp +++ b/src/backup.cpp @@ -1,435 +1,421 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "backup.h" #include "formatimporter.h" // To move a folder #include "global.h" #include "settings.h" #include "tools.h" #include "variouswidgets.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // usleep() /** * Backups are wrapped in a .tar.gz, inside that folder name. * An archive is not a backup or is corrupted if data are not in that folder! */ const QString backupMagicFolder = "BasKet-Note-Pads_Backup"; /** class BackupDialog: */ BackupDialog::BackupDialog(QWidget *parent, const char *name) : QDialog(parent) { setObjectName(name); setModal(true); setWindowTitle(i18n("Backup & Restore")); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); QWidget *page = new QWidget(this); QVBoxLayout *pageVBoxLayout = new QVBoxLayout(page); pageVBoxLayout->setMargin(0); mainLayout->addWidget(page); // pageVBoxLayout->setSpacing(spacingHint()); QString savesFolder = Global::savesFolder(); savesFolder = savesFolder.left(savesFolder.length() - 1); // savesFolder ends with "/" QGroupBox *folderGroup = new QGroupBox(i18n("Save Folder"), page); pageVBoxLayout->addWidget(folderGroup); mainLayout->addWidget(folderGroup); QVBoxLayout *folderGroupLayout = new QVBoxLayout; folderGroup->setLayout(folderGroupLayout); folderGroupLayout->addWidget(new QLabel("" + i18n("Your baskets are currently stored in that folder:
%1", savesFolder), folderGroup)); QWidget *folderWidget = new QWidget; folderGroupLayout->addWidget(folderWidget); QHBoxLayout *folderLayout = new QHBoxLayout(folderWidget); folderLayout->setContentsMargins(0, 0, 0, 0); QPushButton *moveFolder = new QPushButton(i18n("&Move to Another Folder..."), folderWidget); QPushButton *useFolder = new QPushButton(i18n("&Use Another Existing Folder..."), folderWidget); HelpLabel *helpLabel = new HelpLabel(i18n("Why to do that?"), i18n("

You can move the folder where %1 store your baskets to:

    " "
  • Store your baskets in a visible place in your home folder, like ~/Notes or ~/Baskets, so you can manually backup them when you want.
  • " "
  • Store your baskets on a server to share them between two computers.
    " "In this case, mount the shared-folder to the local file system and ask %1 to use that mount point.
    " "Warning: you should not run %1 at the same time on both computers, or you risk to loss data while the two applications are desynced.
  • " "

Please remember that you should not change the content of that folder manually (eg. adding a file in a basket folder will not add that file to the basket).

", QGuiApplication::applicationDisplayName()), folderWidget); folderLayout->addWidget(moveFolder); folderLayout->addWidget(useFolder); folderLayout->addWidget(helpLabel); folderLayout->addStretch(); connect(moveFolder, SIGNAL(clicked()), this, SLOT(moveToAnotherFolder())); connect(useFolder, SIGNAL(clicked()), this, SLOT(useAnotherExistingFolder())); QGroupBox *backupGroup = new QGroupBox(i18n("Backups"), page); pageVBoxLayout->addWidget(backupGroup); mainLayout->addWidget(backupGroup); QVBoxLayout *backupGroupLayout = new QVBoxLayout; backupGroup->setLayout(backupGroupLayout); QWidget *backupWidget = new QWidget; backupGroupLayout->addWidget(backupWidget); QHBoxLayout *backupLayout = new QHBoxLayout(backupWidget); backupLayout->setContentsMargins(0, 0, 0, 0); QPushButton *backupButton = new QPushButton(i18n("&Backup..."), backupWidget); QPushButton *restoreButton = new QPushButton(i18n("&Restore a Backup..."), backupWidget); m_lastBackup = new QLabel("", backupWidget); backupLayout->addWidget(backupButton); backupLayout->addWidget(restoreButton); backupLayout->addWidget(m_lastBackup); backupLayout->addStretch(); connect(backupButton, SIGNAL(clicked()), this, SLOT(backup())); connect(restoreButton, SIGNAL(clicked()), this, SLOT(restore())); populateLastBackup(); (new QWidget(page))->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); buttonBox->button(QDialogButtonBox::Close)->setDefault(true); } BackupDialog::~BackupDialog() { } void BackupDialog::populateLastBackup() { QString lastBackupText = i18n("Last backup: never"); if (Settings::lastBackup().isValid()) lastBackupText = i18n("Last backup: %1", Settings::lastBackup().toString(Qt::LocalDate)); m_lastBackup->setText(lastBackupText); } void BackupDialog::moveToAnotherFolder() { QUrl selectedURL = QFileDialog::getExistingDirectoryUrl(/*parent=*/0, /*caption=*/i18n("Choose a Folder Where to Move Baskets"), /*startDir=*/Global::savesFolder()); if (!selectedURL.isEmpty()) { QString folder = selectedURL.path(); QDir dir(folder); // The folder should not exists, or be empty (because KDirSelectDialog will likely create it anyway): if (dir.exists()) { // Get the content of the folder: QStringList content = dir.entryList(); if (content.count() > 2) { // "." and ".." int result = KMessageBox::questionYesNo(0, "" + i18n("The folder %1 is not empty. Do you want to override it?", folder), i18n("Override Folder?"), KGuiItem(i18n("&Override"), "document-save")); if (result == KMessageBox::No) return; } Tools::deleteRecursively(folder); } FormatImporter copier; copier.moveFolder(Global::savesFolder(), folder); Backup::setFolderAndRestart(folder, i18n("Your baskets have been successfully moved to %1. %2 is going to be restarted to take this change into account.")); } } void BackupDialog::useAnotherExistingFolder() { QUrl selectedURL = QFileDialog::getExistingDirectoryUrl(/*parent=*/0, /*caption=*/i18n("Choose an Existing Folder to Store Baskets"), /*startDir=*/Global::savesFolder()); if (!selectedURL.isEmpty()) { Backup::setFolderAndRestart(selectedURL.path(), i18n("Your basket save folder has been successfully changed to %1. %2 is going to be restarted to take this change into account.")); } } void BackupDialog::backup() { QDir dir; // Compute a default file name & path (eg. "Baskets_2007-01-31.tar.gz"): KConfig *config = KSharedConfig::openConfig().data(); KConfigGroup configGroup(config, "Backups"); QString folder = configGroup.readEntry("lastFolder", QDir::homePath()) + '/'; QString fileName = i18nc("Backup filename (without extension), %1 is the date", "Baskets_%1", QDate::currentDate().toString(Qt::ISODate)); QString url = folder + fileName; // Ask a file name & path to the user: QString filter = "*.tar.gz|" + i18n("Tar Archives Compressed by Gzip") + "\n*|" + i18n("All Files"); QString destination = url; for (bool askAgain = true; askAgain;) { // Ask: destination = QFileDialog::getSaveFileName(NULL, i18n("Backup Baskets"), destination, filter); // User canceled? if (destination.isEmpty()) return; // File already existing? Ask for overriding: if (dir.exists(destination)) { int result = KMessageBox::questionYesNoCancel( 0, "" + i18n("The file %1 already exists. Do you really want to override it?", QUrl::fromLocalFile(destination).fileName()), i18n("Override File?"), KGuiItem(i18n("&Override"), "document-save")); if (result == KMessageBox::Cancel) return; else if (result == KMessageBox::Yes) askAgain = false; } else askAgain = false; } QProgressDialog dialog; dialog.setWindowTitle(i18n("Backup Baskets")); dialog.setLabelText(i18n("Backing up baskets. Please wait...")); dialog.setModal(true); dialog.setCancelButton(NULL); dialog.setAutoClose(true); dialog.setRange(0, 0 /*Busy/Undefined*/); dialog.setValue(0); dialog.show(); /* If needed, uncomment this and call similar code in other places below QProgressBar* progress = new QProgressBar(dialog); progress->setTextVisible(false); dialog.setBar(progress);*/ BackupThread thread(destination, Global::savesFolder()); thread.start(); while (thread.isRunning()) { dialog.setValue(dialog.value() + 1); // Or else, the animation is not played! qApp->processEvents(); usleep(300); // Not too long because if the backup process is finished, we wait for nothing } Settings::setLastBackup(QDate::currentDate()); Settings::saveConfig(); populateLastBackup(); } void BackupDialog::restore() { // Get last backup folder: KConfig *config = KSharedConfig::openConfig().data(); KConfigGroup configGroup(config, "Backups"); QString folder = configGroup.readEntry("lastFolder", QDir::homePath()) + '/'; // Ask a file name to the user: QString filter = "*.tar.gz|" + i18n("Tar Archives Compressed by Gzip") + "\n*|" + i18n("All Files"); QString path = QFileDialog::getOpenFileName(this, i18n("Open Basket Archive"), folder, filter); if (path.isEmpty()) // User has canceled return; // Before replacing the basket data folder with the backup content, we safely backup the current baskets to the home folder. // So if the backup is corrupted or something goes wrong while restoring (power cut...) the user will be able to restore the old working data: QString safetyPath = Backup::newSafetyFolder(); FormatImporter copier; copier.moveFolder(Global::savesFolder(), safetyPath); // Add the README file for user to cancel a bad restoration: QString readmePath = safetyPath + i18n("README.txt"); QFile file(readmePath); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream << i18n("This is a safety copy of your baskets like they were before you started to restore the backup %1.", QUrl::fromLocalFile(path).fileName()) + "\n\n" << i18n("If the restoration was a success and you restored what you wanted to restore, you can remove this folder.") + "\n\n" << i18n("If something went wrong during the restoration process, you can re-use this folder to store your baskets and nothing will be lost.") + "\n\n" << i18n("Choose \"Basket\" -> \"Backup & Restore...\" -> \"Use Another Existing Folder...\" and select that folder.") + '\n'; file.close(); } QString message = "

" + i18n("Restoring %1. Please wait...", QUrl::fromLocalFile(path).fileName()) + "

" + i18n("If something goes wrong during the restoration process, read the file %1.", readmePath); QProgressDialog *dialog = new QProgressDialog(); dialog->setWindowTitle(i18n("Restore Baskets")); dialog->setLabelText(message); dialog->setModal(/*modal=*/true); dialog->setCancelButton(NULL); dialog->setAutoClose(true); dialog->setRange(0, 0 /*Busy/Undefined*/); dialog->setValue(0); dialog->show(); // Uncompress: RestoreThread thread(path, Global::savesFolder()); thread.start(); while (thread.isRunning()) { dialog->setValue(dialog->value() + 1); // Or else, the animation is not played! qApp->processEvents(); usleep(300); // Not too long because if the restore process is finished, we wait for nothing } dialog->hide(); // The restore is finished, do not continue to show it while telling the user the application is going to be restarted delete dialog; // If we only hidden it, it reappeared just after having restored a small backup... Very strange. dialog = 0; // This was annoying since it is modal and the "BasKet Note Pads is going to be restarted" message was not reachable. // qApp->processEvents(); // Check for errors: if (!thread.success()) { // Restore the old baskets: QDir dir; dir.remove(readmePath); copier.moveFolder(safetyPath, Global::savesFolder()); // Tell the user: KMessageBox::error(0, i18n("This archive is either not a backup of baskets or is corrupted. It cannot be imported. Your old baskets have been preserved instead."), i18n("Restore Error")); return; } // Note: The safety backup is not removed now because the code can has been wrong, somehow, or the user perhapse restored an older backup by error... // The restore process will not be called very often (it is possible it will only be called once or twice around the world during the next years). // So it is rare enough to force the user to remove the safety folder, but keep him in control and let him safely recover from restoration errors. Backup::setFolderAndRestart(Global::savesFolder() /*No change*/, i18n("Your backup has been successfully restored to %1. %2 is going to be restarted to take this change into account.")); } /** class Backup: */ QString Backup::binaryPath = ""; void Backup::figureOutBinaryPath(const char *argv0, QApplication &app) { /* The application can be launched by two ways: - Globally (app.applicationFilePath() is good) - In KDevelop or with an absolute path (app.applicationFilePath() is wrong) This function is called at the very start of main() so that the current directory has not been changed yet. Command line (argv[0]) QDir(argv[0]).canonicalPath() app.applicationFilePath() ====================== ============================================= ========================= "basket" "" "/opt/kde3/bin/basket" "./src/.libs/basket" "/home/seb/prog/basket/debug/src/.lib/basket" "/opt/kde3/bin/basket" */ binaryPath = QDir(argv0).canonicalPath(); if (binaryPath.isEmpty()) binaryPath = app.applicationFilePath(); } void Backup::setFolderAndRestart(const QString &folder, const QString &message) { // Set the folder: Settings::setDataFolder(folder); Settings::saveConfig(); // Reassure the user that the application main window disappearance is not a crash, but a normal restart. // This is important for users to trust the application in such a critical phase and understands what's happening: KMessageBox::information(0, "" + message.arg((folder.endsWith('/') ? folder.left(folder.length() - 1) : folder), QGuiApplication::applicationDisplayName()), i18n("Restart")); // Restart the application: KRun::runCommand(binaryPath, QCoreApplication::applicationName(), QCoreApplication::applicationName(), 0); exit(0); } QString Backup::newSafetyFolder() { QDir dir; QString fullPath; fullPath = QDir::homePath() + '/' + i18nc("Safety folder name before restoring a basket data archive", "Baskets Before Restoration") + '/'; if (!dir.exists(fullPath)) return fullPath; for (int i = 2;; ++i) { fullPath = QDir::homePath() + '/' + i18nc("Safety folder name before restoring a basket data archive", "Baskets Before Restoration (%1)", i) + '/'; if (!dir.exists(fullPath)) return fullPath; } return ""; } /** class BackupThread: */ BackupThread::BackupThread(const QString &tarFile, const QString &folderToBackup) : m_tarFile(tarFile) , m_folderToBackup(folderToBackup) { } void BackupThread::run() { KTar tar(m_tarFile, "application/x-gzip"); tar.open(QIODevice::WriteOnly); tar.addLocalDirectory(m_folderToBackup, backupMagicFolder); // KArchive does not add hidden files. Basket description files (".basket") are hidden, we add them manually: QDir dir(m_folderToBackup + "baskets/"); QStringList baskets = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); for (QStringList::Iterator it = baskets.begin(); it != baskets.end(); ++it) { tar.addLocalFile(m_folderToBackup + "baskets/" + *it + "/.basket", backupMagicFolder + "/baskets/" + *it + "/.basket"); } // We finished: tar.close(); } /** class RestoreThread: */ RestoreThread::RestoreThread(const QString &tarFile, const QString &destFolder) : m_tarFile(tarFile) , m_destFolder(destFolder) { } void RestoreThread::run() { m_success = false; KTar tar(m_tarFile, "application/x-gzip"); tar.open(QIODevice::ReadOnly); if (tar.isOpen()) { const KArchiveDirectory *directory = tar.directory(); if (directory->entries().contains(backupMagicFolder)) { const KArchiveEntry *entry = directory->entry(backupMagicFolder); if (entry->isDirectory()) { ((const KArchiveDirectory *)entry)->copyTo(m_destFolder); m_success = true; } } tar.close(); } } diff --git a/src/backup.h b/src/backup.h index da7f74e..7054c81 100644 --- a/src/backup.h +++ b/src/backup.h @@ -1,97 +1,83 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef BACKUP_H #define BACKUP_H #include #include class QApplication; class QLabel; #include "basket_export.h" /** * @author Sébastien Laoût */ class BackupDialog : public QDialog { Q_OBJECT public: explicit BackupDialog(QWidget *parent = 0, const char *name = 0); ~BackupDialog() override; private slots: void moveToAnotherFolder(); void useAnotherExistingFolder(); void backup(); void restore(); void populateLastBackup(); private: QLabel *m_lastBackup; }; /** * @author Sébastien Laoût */ class BASKET_EXPORT Backup { public: static void figureOutBinaryPath(const char *argv0, QApplication &app); static void setFolderAndRestart(const QString &folder, const QString &message); static QString newSafetyFolder(); private: static QString binaryPath; }; class BackupThread : public QThread { public: BackupThread(const QString &tarFile, const QString &folderToBackup); protected: void run() override; private: QString m_tarFile; QString m_folderToBackup; }; class RestoreThread : public QThread { public: RestoreThread(const QString &tarFile, const QString &destFolder); inline bool success() { return m_success; } protected: void run() override; private: QString m_tarFile; QString m_destFolder; bool m_success; }; #endif // BACKUP_H diff --git a/src/basket_export.h b/src/basket_export.h index 8a429e4..1c161f7 100644 --- a/src/basket_export.h +++ b/src/basket_export.h @@ -1,37 +1,23 @@ -/* This file is part of Basket - Copyright (C) 2009 Matt Rogers - - 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. -*/ +/** + * SPDX-FileCopyrightText: (C) (C) 2009 Matt Rogers + * SPDX-License-Identifier: LGPL-2.1-or-later + */ #ifndef BASKET_EXPORT_H #define BASKET_EXPORT_H #ifndef BASKET_EXPORT #if defined(MAKE_BASKETCOMMON_LIB) /* We are building this library */ #define BASKET_EXPORT Q_DECL_EXPORT #else /* We are using this library */ #define BASKET_EXPORT Q_DECL_IMPORT #endif #endif #ifndef BASKET_EXPORT_DEPRECATED #define BASKET_EXPORT_DEPRECATED KDE_DEPRECATED BASKET_EXPORT #endif #endif diff --git a/src/basket_options.h b/src/basket_options.h index 5ecc295..7ae6bd7 100644 --- a/src/basket_options.h +++ b/src/basket_options.h @@ -1,52 +1,38 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef BASKET_OPTIONS_H #define BASKET_OPTIONS_H #include #include #include #include "aboutdata.h" #include "global.h" void setupCmdLineOptions(QCommandLineParser *opts) { opts->addHelpOption(); opts->addVersionOption(); opts->addOption(QCommandLineOption(QStringList() << "d" << "debug", i18n("Show the debug window"))); opts->addOption(QCommandLineOption(QStringList() << "f" << "data-folder", i18n("Custom folder to load and save baskets and other application data."), i18nc("Command line help: --data-folder ", "folder"))); opts->addOption(QCommandLineOption("start-hidden", i18n("Automatically hide the main window in the system tray on startup."))); // opts->addOption(QCommandLineOption(QStringList() << "k" << "use-drkonqi", i18n("On crash, use the standard KDE crash handler rather than send an email."))); opts->addPositionalArgument("file", i18n("Open a basket archive or template.")); } #endif // BASKET_OPTIONS_H diff --git a/src/basket_part.cpp b/src/basket_part.cpp index 84af1f7..f9589e8 100644 --- a/src/basket_part.cpp +++ b/src/basket_part.cpp @@ -1,95 +1,80 @@ -/*************************************************************************** - * Copyright (C) 2003 by Petri Damsten * - * petri.damsten@iki.fi * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Petri Damsten + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "basket_part.h" #include #include "aboutdata.h" #include "basketstatusbar.h" #include "bnpview.h" K_PLUGIN_FACTORY_DEFINITION(BasketFactory, registerPlugin();) BasketPart::BasketPart(QWidget *parentWidget, QObject *parent, const QList &) : KParts::ReadWritePart(parent) { // we need an instance // setInstance( BasketFactory::instance() ); BasketStatusBar *bar = new BasketStatusBar(new KParts::StatusBarExtension(this)); // this should be your custom internal widget m_view = new BNPView(parentWidget, "BNPViewPart", this, actionCollection(), bar); connect(m_view, SIGNAL(setWindowCaption(const QString &)), this, SLOT(setWindowTitle(const QString &))); connect(m_view, SIGNAL(showPart()), this, SIGNAL(showPart())); m_view->setFocusPolicy(Qt::ClickFocus); // notify the part that this is our internal widget setWidget(m_view); setComponentName(AboutData::componentName(), AboutData::displayName()); // set our XML-UI resource file setXMLFile("basket_part.rc", true); // we are read-write by default setReadWrite(true); // we are not modified since we haven't done anything yet setModified(false); } BasketPart::~BasketPart() { } void BasketPart::setReadWrite(bool rw) { // TODO: notify your internal widget of the read-write state ReadWritePart::setReadWrite(rw); } void BasketPart::setModified(bool modified) { // in any event, we want our parent to do it's thing ReadWritePart::setModified(modified); } bool BasketPart::openFile() { // TODO return false; } bool BasketPart::saveFile() { // TODO return false; } KAboutData *BasketPart::createAboutData() { return new AboutData(); } void BasketPart::setWindowTitle(const QString &caption) { emit setWindowCaption(caption); } diff --git a/src/basket_part.h b/src/basket_part.h index 33e6ee1..0b686fb 100644 --- a/src/basket_part.h +++ b/src/basket_part.h @@ -1,94 +1,79 @@ -/*************************************************************************** - * Copyright (C) 2003 by Petri Damsten * - * petri.damsten@iki.fi * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Petri Damsten + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef _BASKETPART_H_ #define _BASKETPART_H_ #include #include class QWidget; class QPainter; class KAboutData; class QUrl; class BNPView; /** * This is a "Part". It that does all the real work in a KPart * application. * * @short Main Part * @author Petri Damsten * @version 0.1 */ class BasketPart : public KParts::ReadWritePart { Q_OBJECT public: /** * Default constructor */ BasketPart(QWidget *parentWidget, QObject *parent, const QList &); /** * Destructor */ virtual ~BasketPart(); /** * This is a virtual function inherited from KParts::ReadWritePart. * A shell will use this to inform this Part if it should act * read-only */ virtual void setReadWrite(bool rw); /** * Reimplemented to disable and enable Save action */ virtual void setModified(bool modified); static KAboutData *createAboutData(); signals: void showPart(); protected: /** * This must be implemented by each part */ virtual bool openFile(); /** * This must be implemented by each read-write part */ virtual bool saveFile(); protected slots: void setWindowTitle(const QString &caption); private: BNPView *m_view; }; K_PLUGIN_FACTORY_DECLARATION(BasketFactory) #endif // _BASKETPART_H_ diff --git a/src/basket_plugin.cpp b/src/basket_plugin.cpp index 722eeb5..7923d53 100644 --- a/src/basket_plugin.cpp +++ b/src/basket_plugin.cpp @@ -1,67 +1,52 @@ -/*************************************************************************** - * Copyright (C) 2009 by Robert Marmorstein * - * robert@narnia.homeunix.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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2009 by Robert Marmorstein + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "basket_plugin.h" #include "global.h" #include "basket_part.h" #include EXPORT_KONTACT_PLUGIN(BasketPlugin, basket) BasketPlugin::BasketPlugin(KontactInterface::Core *core, const QVariantList &) : KontactInterface::Plugin(core, core, "Basket") { setComponentData(KontactPluginFactory::componentData()); Global::basketConfig = KSharedConfig::openConfig("basketrc"); } BasketPlugin::~BasketPlugin() { } KParts::ReadOnlyPart *BasketPlugin::createPart() { KParts::ReadOnlyPart *part = loadPart(); connect(part, SIGNAL(showPart()), this, SLOT(showPart())); return part; } void BasketPlugin::readProperties(const KConfigGroup &config) { if (part()) { BasketPart *myPart = static_cast(part()); } } void BasketPlugin::saveProperties(KConfigGroup &config) { if (part()) { BasketPart *myPart = static_cast(part()); } } void BasketPlugin::showPart() { core()->selectPlugin(this); } diff --git a/src/basket_plugin.h b/src/basket_plugin.h index b04a6cf..c6235fa 100644 --- a/src/basket_plugin.h +++ b/src/basket_plugin.h @@ -1,49 +1,34 @@ -/*************************************************************************** - * Copyright (C) 2009 by Robert Marmorstein * - * robert@narnia.homeunix.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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2009 by Robert Marmorstein + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef BASKET_PLUGIN_H #define BASKET_PLUGIN_H #include namespace KParts { class ReadOnlyPart; } class BasketPlugin : public KontactInterface::Plugin { Q_OBJECT public: BasketPlugin(KontactInterface::Core *core, const QVariantList &); ~BasketPlugin(); virtual void readProperties(const KConfigGroup &config); virtual void saveProperties(KConfigGroup &config); private slots: void showPart(); protected: KParts::ReadOnlyPart *createPart(); }; #endif diff --git a/src/basketfactory.cpp b/src/basketfactory.cpp index a70c3ab..9a3307f 100644 --- a/src/basketfactory.cpp +++ b/src/basketfactory.cpp @@ -1,161 +1,147 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "basketfactory.h" #include #include #include #include #include #include #include #include "basketscene.h" #include "bnpview.h" #include "global.h" #include "note.h" // For balanced column width computation #include "xmlwork.h" /** BasketFactory */ // TODO: Don't create a basket with a name that already exists! QString BasketFactory::newFolderName() { QString folderName; QString fullPath; QDir dir; int i = QDir(Global::basketsFolder()).count(); QString time = QTime::currentTime().toString("hhmmss"); for (;; ++i) { folderName = QString("basket%1-%2/").arg(i).arg(time); fullPath = Global::basketsFolder() + folderName; dir = QDir(fullPath); if (!dir.exists()) // OK : The folder do not yet exists : break; // We've found one ! } return folderName; } QString BasketFactory::unpackTemplate(const QString &templateName) { // Find a name for a new folder and create it: QString folderName = newFolderName(); QString fullPath = Global::basketsFolder() + folderName; QDir dir; if (!dir.mkpath(fullPath)) { KMessageBox::error(/*parent=*/0, i18n("Sorry, but the folder creation for this new basket has failed."), i18n("Basket Creation Failed")); return ""; } // Unpack the template file to that folder: // TODO: REALLY unpack (this hand-creation is temporary, or it could be used in case the template can't be found) QFile file(fullPath + "/.basket"); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream.setCodec("UTF-8"); int nbColumns = (templateName == "mindmap" || templateName == "free" ? 0 : templateName.left(1).toInt()); BasketScene *currentBasket = Global::bnpView->currentBasket(); int columnWidth = (currentBasket && nbColumns > 0 ? (currentBasket->graphicsView()->viewport()->width() - (nbColumns - 1) * Note::RESIZER_WIDTH) / nbColumns : 0); stream << QString( "\n" "\n" "\n" " \n" " \n" " \n" " \n") .arg((templateName == "mindmap" ? "true" : "false"), QString::number(nbColumns), (templateName == "free" || templateName == "mindmap" ? "true" : "false")); if (nbColumns > 0) for (int i = 0; i < nbColumns; ++i) stream << QString(" \n").arg(columnWidth); stream << " \n" "\n"; file.close(); return folderName; } else { KMessageBox::error(/*parent=*/0, i18n("Sorry, but the template copying for this new basket has failed."), i18n("Basket Creation Failed")); return ""; } } void BasketFactory::newBasket(const QString &icon, const QString &name, const QString &backgroundImage, const QColor &backgroundColor, const QColor &textColor, const QString &templateName, BasketScene *parent) { // Unpack the templateName file to a new basket folder: QString folderName = unpackTemplate(templateName); if (folderName.isEmpty()) return; // Read the properties, change those that should be customized and save the result: QDomDocument *document = XMLWork::openFile("basket", Global::basketsFolder() + folderName + "/.basket"); if (!document) { KMessageBox::error(/*parent=*/0, i18n("Sorry, but the template customization for this new basket has failed."), i18n("Basket Creation Failed")); return; } QDomElement properties = XMLWork::getElement(document->documentElement(), "properties"); if (!icon.isEmpty()) { QDomElement iconElement = XMLWork::getElement(properties, "icon"); if (!iconElement.tagName().isEmpty()) // If there is already an icon, remove it since we will add our own value below iconElement.removeChild(iconElement.firstChild()); XMLWork::addElement(*document, properties, "icon", icon); } if (!name.isEmpty()) { QDomElement nameElement = XMLWork::getElement(properties, "name"); if (!nameElement.tagName().isEmpty()) // If there is already a name, remove it since we will add our own value below nameElement.removeChild(nameElement.firstChild()); XMLWork::addElement(*document, properties, "name", name); } if (backgroundColor.isValid()) { QDomElement appearanceElement = XMLWork::getElement(properties, "appearance"); if (appearanceElement.tagName().isEmpty()) { // If there is not already an appearance tag, add it since we will access it below appearanceElement = document->createElement("appearance"); properties.appendChild(appearanceElement); } appearanceElement.setAttribute("backgroundColor", backgroundColor.name()); } if (!backgroundImage.isEmpty()) { QDomElement appearanceElement = XMLWork::getElement(properties, "appearance"); if (appearanceElement.tagName().isEmpty()) { // If there is not already an appearance tag, add it since we will access it below appearanceElement = document->createElement("appearance"); properties.appendChild(appearanceElement); } appearanceElement.setAttribute("backgroundImage", backgroundImage); } if (textColor.isValid()) { QDomElement appearanceElement = XMLWork::getElement(properties, "appearance"); if (appearanceElement.tagName().isEmpty()) { // If there is not already an appearance tag, add it since we will access it below appearanceElement = document->createElement("appearance"); properties.appendChild(appearanceElement); } appearanceElement.setAttribute("textColor", textColor.name()); } // Load it in the parent basket (it will save the tree and switch to this new basket): Global::bnpView->loadNewBasket(folderName, properties, parent); } diff --git a/src/basketfactory.h b/src/basketfactory.h index e6d5a19..20be33d 100644 --- a/src/basketfactory.h +++ b/src/basketfactory.h @@ -1,40 +1,26 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef BASKETFACTORY_H #define BASKETFACTORY_H class QColor; class QString; class BasketScene; /** Methods to create various baskets (mkdir, init the properties and load it). * @author Sébastien Laoût */ namespace BasketFactory { /** You should use this method to create a new basket: */ void newBasket(const QString &icon, const QString &name, const QString &backgroundImage, const QColor &backgroundColor, const QColor &textColor, const QString &templateName, BasketScene *parent); /** Internal tool methods to process the method above: */ QString newFolderName(); QString unpackTemplate(const QString &templateName); } #endif // BASKETFACTORY_H diff --git a/src/basketlistview.cpp b/src/basketlistview.cpp index ca4db0d..1f51e70 100644 --- a/src/basketlistview.cpp +++ b/src/basketlistview.cpp @@ -1,655 +1,641 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "basketlistview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "basketscene.h" #include "bnpview.h" #include "decoratedbasket.h" #include "global.h" #include "icon_names.h" #include "notedrag.h" #include "settings.h" #include "tools.h" /** class BasketListViewItem: */ BasketListViewItem::BasketListViewItem(QTreeWidget *parent, BasketScene *basket) : QTreeWidgetItem(parent) , m_basket(basket) , m_isUnderDrag(false) , m_isAbbreviated(false) { } BasketListViewItem::BasketListViewItem(QTreeWidgetItem *parent, BasketScene *basket) : QTreeWidgetItem(parent) , m_basket(basket) , m_isUnderDrag(false) , m_isAbbreviated(false) { } BasketListViewItem::BasketListViewItem(QTreeWidget *parent, QTreeWidgetItem *after, BasketScene *basket) : QTreeWidgetItem(parent, after) , m_basket(basket) , m_isUnderDrag(false) , m_isAbbreviated(false) { } BasketListViewItem::BasketListViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, BasketScene *basket) : QTreeWidgetItem(parent, after) , m_basket(basket) , m_isUnderDrag(false) , m_isAbbreviated(false) { } BasketListViewItem::~BasketListViewItem() { } QString BasketListViewItem::escapedName(const QString &string) { // Underlining the Alt+Letter shortcut (and escape all other '&' characters), if any: QString basketName = string; basketName.replace('&', "&&"); // First escape all the amperstamp QString letter; QRegExp letterExp("^Alt\\+(?:Shift\\+)?(.)$"); QString basketShortcut = m_basket->shortcut().toString(); if (letterExp.indexIn(basketShortcut) != -1) { int index; letter = letterExp.cap(1); if ((index = basketName.indexOf(letter)) != -1) basketName.insert(index, '&'); } return basketName; } void BasketListViewItem::setup() { setText(/*column=*/0, escapedName(m_basket->basketName())); QPixmap icon = KIconLoader::global()->loadIcon(m_basket->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, /*canReturnNull=*/false); setIcon(/*column=*/0, icon); /* QBrush brush; bool withIcon = m_stateCopy || (m_tagCopy && !m_tagCopy->isMultiState()); State* state = (m_tagCopy ? m_tagCopy->stateCopies[0]->newState : m_stateCopy->newState); brush.setColor(isSelected() ? qApp->palette().color(QPalette::Highlight) : (withIcon && state->backgroundColor().isValid() ? state->backgroundColor() : viewport->palette().color(viewwport->backgroundRole()))); setBackground(brush); */ } BasketListViewItem *BasketListViewItem::lastChild() { int count = childCount(); if (count <= 0) return 0; return (BasketListViewItem *)(child(count - 1)); } QStringList BasketListViewItem::childNamesTree(int deep) { QStringList result; // Compute indentation spaces: QString spaces; for (int j = 0; j < deep; ++j) spaces += " "; // Append the names of sub baskets if (deep > 0) result.append(spaces + basket()->basketName()); // Append the children: for (int i = 0; i < childCount(); i++) { QStringList children = ((BasketListViewItem *)child(i))->childNamesTree(deep + 1); result.append(children); } return result; } void BasketListViewItem::moveChildsBaskets() { int insertAfterThis = 0; if (!parent()) insertAfterThis = treeWidget()->indexOfTopLevelItem(this); for (int i = 0; i < childCount(); i++) { // Re-insert the item with the good parent: if (parent()) parent()->insertChild(insertAfterThis, child(i)); else treeWidget()->insertTopLevelItem(insertAfterThis, child(i)); // And move it at the good place: insertAfterThis++; } } void BasketListViewItem::ensureVisible() { BasketListViewItem *item = this; while (item->parent()) { item = (BasketListViewItem *)(item->parent()); item->setExpanded(true); } } bool BasketListViewItem::isShown() { QTreeWidgetItem *item = parent(); while (item) { if (!item->isExpanded()) return false; item = item->parent(); } return true; } bool BasketListViewItem::isCurrentBasket() { return basket() == Global::bnpView->currentBasket(); } bool BasketListViewItem::isUnderDrag() { return m_isUnderDrag; } bool BasketListViewItem::haveChildsLoading() { for (int i = 0; i < childCount(); i++) { BasketListViewItem *childItem = (BasketListViewItem *)child(i); if (!childItem->basket()->isLoaded() && !childItem->basket()->isLocked()) return true; if (childItem->haveChildsLoading()) return true; } return false; } bool BasketListViewItem::haveHiddenChildsLoading() { if (isExpanded()) return false; return haveChildsLoading(); } bool BasketListViewItem::haveChildsLocked() { for (int i = 0; i < childCount(); i++) { BasketListViewItem *childItem = (BasketListViewItem *)child(i); if (/*!*/ childItem->basket()->isLocked()) return true; if (childItem->haveChildsLocked()) return true; } return false; } bool BasketListViewItem::haveHiddenChildsLocked() { if (isExpanded()) return false; return haveChildsLocked(); } int BasketListViewItem::countChildsFound() { int count = 0; for (int i = 0; i < childCount(); i++) { BasketListViewItem *childItem = (BasketListViewItem *)child(i); count += childItem->basket()->countFounds(); count += childItem->countChildsFound(); } return count; } int BasketListViewItem::countHiddenChildsFound() { if (isExpanded()) return 0; return countChildsFound(); } void BasketListViewItem::setUnderDrag(bool underDrag) { m_isUnderDrag = underDrag; } bool BasketListViewItem::isAbbreviated() { return m_isAbbreviated; } void BasketListViewItem::setAbbreviated(bool b) { m_isAbbreviated = b; } /** class BasketTreeListView: */ QString BasketTreeListView::TREE_ITEM_MIME_STRING = "application/x-basket-item"; BasketTreeListView::BasketTreeListView(QWidget *parent) : QTreeWidget(parent) , m_autoOpenItem(0) , m_itemUnderDrag(0) { connect(&m_autoOpenTimer, SIGNAL(timeout()), this, SLOT(autoOpen())); setItemDelegate(new FoundCountIcon(this)); } void BasketTreeListView::contextMenuEvent(QContextMenuEvent *e) { emit contextMenuRequested(e->pos()); } QStringList BasketTreeListView::mimeTypes() const { QStringList types; types << TREE_ITEM_MIME_STRING; types << NoteDrag::NOTE_MIME_STRING; return types; } QMimeData *BasketTreeListView::mimeData(const QList items) const { QString mimeType = TREE_ITEM_MIME_STRING; QByteArray data = QByteArray(); QDataStream out(&data, QIODevice::WriteOnly); if (items.isEmpty()) return new QMimeData(); for (int i = 0; i < items.count(); ++i) { BasketListViewItem *basketItem = static_cast(items[i]); out << basketItem->basket()->basketName() << basketItem->basket()->folderName() << basketItem->basket()->icon(); } QMimeData *mimeData = new QMimeData(); mimeData->setData(mimeType, data); return mimeData; } bool BasketTreeListView::event(QEvent *e) { if (e->type() == QEvent::ToolTip) { QHelpEvent *he = static_cast(e); QTreeWidgetItem *item = itemAt(he->pos()); BasketListViewItem *bitem = dynamic_cast(item); if (bitem && bitem->isAbbreviated()) { QRect rect = visualItemRect(bitem); QToolTip::showText(rect.topLeft(), bitem->basket()->basketName(), viewport(), rect); } return true; } return QTreeWidget::event(e); } void BasketTreeListView::mousePressEvent(QMouseEvent *event) { m_dragStartPosition = event->pos(); QTreeWidget::mousePressEvent(event); } void BasketTreeListView::mouseMoveEvent(QMouseEvent *event) { // QTreeWidget::mouseMoveEvent(event); if (!(event->buttons() & Qt::LeftButton)) { event->ignore(); return; } if ((event->pos() - m_dragStartPosition).manhattanLength() < QApplication::startDragDistance()) { event->ignore(); return; } QDrag *drag = new QDrag(this); QMimeData *mimeData = this->mimeData(this->selectedItems()); drag->setMimeData(mimeData); Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction); if (dropAction == Qt::MoveAction || dropAction == Qt::CopyAction) event->accept(); } void BasketTreeListView::dragEnterEvent(QDragEnterEvent *event) { // TODO: accept everything? (forwarding dropped basket-notes and arbitrary data to the basket) // files: MoveAction vs. CopyAction, or acceptProposedAction() QTreeWidget::dragEnterEvent(event); } void BasketTreeListView::removeExpands() { QTreeWidgetItemIterator it(this); while (*it) { QTreeWidgetItem *item = *it; if (item->childCount() <= 0) item->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicatorWhenChildless); ++it; } } void BasketTreeListView::dragLeaveEvent(QDragLeaveEvent *event) { qDebug() << "BasketTreeListView::dragLeaveEvent"; m_autoOpenItem = 0; m_autoOpenTimer.stop(); setItemUnderDrag(0); removeExpands(); QTreeWidget::dragLeaveEvent(event); } void BasketTreeListView::dropEvent(QDropEvent *event) { if (event->mimeData()->hasFormat(TREE_ITEM_MIME_STRING)) { event->setDropAction(Qt::MoveAction); QTreeWidget::dropEvent(event); } else { // this handels application/x-basket-note drag events. qDebug() << "Forwarding dropped data to the basket"; event->setDropAction(Qt::MoveAction); QTreeWidgetItem *item = itemAt(event->pos()); BasketListViewItem *bitem = dynamic_cast(item); if (bitem) { bitem->basket()->blindDrop(event->mimeData(), event->dropAction(), event->source()); } else { qDebug() << "Forwarding failed: no bitem found"; } } m_autoOpenItem = 0; m_autoOpenTimer.stop(); setItemUnderDrag(0); removeExpands(); Global::bnpView->save(); // TODO: Don't save if it was not a basket drop... } void BasketTreeListView::dragMoveEvent(QDragMoveEvent *event) { // qDebug() << "BasketTreeListView::dragMoveEvent"; if (!event->mimeData()->hasFormat(TREE_ITEM_MIME_STRING)) { QTreeWidgetItem *item = itemAt(event->pos()); BasketListViewItem *bitem = dynamic_cast(item); if (m_autoOpenItem != item) { m_autoOpenItem = item; m_autoOpenTimer.setSingleShot(true); m_autoOpenTimer.start(1700); } if (item) { event->accept(); } setItemUnderDrag(bitem); } QTreeWidget::dragMoveEvent(event); } void BasketTreeListView::setItemUnderDrag(BasketListViewItem *item) { if (m_itemUnderDrag != item) { if (m_itemUnderDrag) { // Remove drag status from the old item m_itemUnderDrag->setUnderDrag(false); } m_itemUnderDrag = item; if (m_itemUnderDrag) { // add drag status to the new item m_itemUnderDrag->setUnderDrag(true); } } } void BasketTreeListView::autoOpen() { BasketListViewItem *item = (BasketListViewItem *)m_autoOpenItem; if (item) Global::bnpView->setCurrentBasket(item->basket()); } void BasketTreeListView::resizeEvent(QResizeEvent *event) { QTreeWidget::resizeEvent(event); } /** We should NEVER get focus (because of QWidget::NoFocus focusPolicy()) * but QTreeView can programatically give us the focus. * So we give it to the basket. */ void BasketTreeListView::focusInEvent(QFocusEvent *) { BasketScene *basket = Global::bnpView->currentBasket(); if (basket) basket->setFocus(); } Qt::DropActions BasketTreeListView::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } BasketListViewItem *BasketTreeListView::getBasketInTree(const QModelIndex &index) const { QTreeWidgetItem *item = itemFromIndex(index); return dynamic_cast(item); } void FoundCountIcon::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyledItemDelegate::paint(painter, option, index); // Get access to basket pointer BasketListViewItem *basketInTree = m_basketTree->getBasketInTree(index); if (basketInTree == NULL) return; const int BASKET_ICON_SIZE = 16; // [replace with m_basketTree->iconSize()] const int MARGIN = 1; BasketScene *basket = basketInTree->basket(); // If we are filtering all baskets, and are effectively filtering on something: bool showLoadingIcon = false; bool showEncryptedIcon = false; QPixmap countPixmap; bool showCountPixmap = Global::bnpView->isFilteringAllBaskets() && Global::bnpView->currentBasket()->decoration()->filterBar()->filterData().isFiltering; if (showCountPixmap) { showLoadingIcon = (!basket->isLoaded() && !basket->isLocked()) || basketInTree->haveHiddenChildsLoading(); showEncryptedIcon = basket->isLocked() || basketInTree->haveHiddenChildsLocked(); bool childrenAreLoading = basketInTree->haveHiddenChildsLoading() || basketInTree->haveHiddenChildsLocked(); countPixmap = foundCountPixmap(!basket->isLoaded(), basket->countFounds(), childrenAreLoading, basketInTree->countHiddenChildsFound(), m_basketTree->font(), option.rect.height() - 2 * MARGIN); } int effectiveWidth = option.rect.right() - (countPixmap.isNull() ? 0 : countPixmap.width() + MARGIN) - (showLoadingIcon || showEncryptedIcon ? BASKET_ICON_SIZE + MARGIN : 0); bool drawRoundRect = basket->backgroundColorSetting().isValid() || basket->textColorSetting().isValid(); // Draw the rounded rectangle: if (drawRoundRect) { QPixmap roundRectBmp; QColor background = basket->backgroundColor(); int textWidth = m_basketTree->fontMetrics().width(basketInTree->text(/*column=*/0)); int iconTextMargin = m_basketTree->style()->pixelMetric(QStyle::PM_FocusFrameHMargin); ///< Space between icon and text // Don't forget to update the key computation if parameters // affecting the rendering logic change QString key = QString("BLIRR::%1.%2.%3.%4").arg(option.rect.width()).arg(option.rect.size().height()).arg(textWidth).arg(background.rgb()); if (QPixmap *cached = QPixmapCache::find(key)) { // Qt's documentation recommends copying the pointer // into a QPixmap immediately roundRectBmp = *cached; } else { // Draw first time roundRectBmp = QPixmap(option.rect.size()); roundRectBmp.fill(Qt::transparent); QPainter brushPainter(&roundRectBmp); int cornerR = option.rect.height() / 2 - MARGIN; QRect roundRect(0, MARGIN, BASKET_ICON_SIZE + iconTextMargin + textWidth + 2 * cornerR, option.rect.height() - 2 * MARGIN); brushPainter.setPen(background); brushPainter.setBrush(background); brushPainter.setRenderHint(QPainter::Antialiasing); brushPainter.drawRoundedRect(roundRect, cornerR, cornerR); QPixmapCache::insert(key, roundRectBmp); } basketInTree->setBackground(0, QBrush(roundRectBmp)); basketInTree->setForeground(0, QBrush(basket->textColor())); } // end if drawRoundRect // Render icons on the right int y = option.rect.center().y() - BASKET_ICON_SIZE / 2; if (!countPixmap.isNull()) { painter->drawPixmap(effectiveWidth, y, countPixmap); effectiveWidth += countPixmap.width() + MARGIN; } if (showLoadingIcon) { QPixmap icon = KIconLoader::global()->loadIcon(IconNames::LOADING, KIconLoader::NoGroup, BASKET_ICON_SIZE); painter->drawPixmap(effectiveWidth, y, icon); effectiveWidth += BASKET_ICON_SIZE + MARGIN; } if (showEncryptedIcon && !showLoadingIcon) { QPixmap icon = KIconLoader::global()->loadIcon(IconNames::LOCKED, KIconLoader::NoGroup, BASKET_ICON_SIZE); painter->drawPixmap(effectiveWidth, y, icon); } } QPixmap FoundCountIcon::circledTextPixmap(const QString &text, int height, const QFont &font, const QColor &color) const { QString key = QString("BLI-%1.%2.%3.%4").arg(text).arg(height).arg(font.toString()).arg(color.rgb()); if (QPixmap *cached = QPixmapCache::find(key)) { return *cached; } // Compute the sizes of the image components: QRectF textRect = QFontMetrics(font).boundingRect(0, 0, /*width=*/1, height, Qt::AlignLeft | Qt::AlignTop, text); qreal xMargin = height / 6; qreal width = xMargin + textRect.width() + xMargin; // Create the background image: QPixmap background(3 * width, 3 * height); // We double the size to be able to smooth scale down it (== antialiased curves) QPainter backgroundPainter(&background); const QPalette &palette = m_basketTree->palette(); backgroundPainter.fillRect(0, 0, background.width(), background.height(), palette.color(QPalette::Highlight)); backgroundPainter.end(); // Draw the curved rectangle: QBitmap curvedRectangle(3 * width, 3 * height); curvedRectangle.fill(Qt::color0); QPainter curvePainter(&curvedRectangle); curvePainter.setPen(Qt::color1); curvePainter.setBrush(Qt::color1); curvePainter.setClipRect(0, 0, 3 * (height / 5), 3 * (height)); // If the width is small, don't fill the right part of the pixmap curvePainter.drawEllipse(0, 3 * (-height / 4), 3 * (height), 3 * (height * 3 / 2)); // Don't forget we double the sizes curvePainter.setClipRect(3 * (width - height / 5), 0, 3 * (height / 5), 3 * (height)); curvePainter.drawEllipse(3 * (width - height), 3 * (-height / 4), 3 * (height), 3 * (height * 3 / 2)); curvePainter.setClipping(false); curvePainter.fillRect(3 * (height / 6), 0, 3 * (width - 2 * height / 6), 3 * (height), curvePainter.brush()); curvePainter.end(); // Apply the curved rectangle as the mask of the background: background.setMask(curvedRectangle); QImage resultImage = background.toImage(); // resultImage.setAlphaBuffer(true); resultImage.convertToFormat(QImage::Format_ARGB32); // Scale down the image smoothly to get anti-aliasing: QPixmap pmScaled = QPixmap::fromImage(resultImage.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); // Draw the text, and return the result: QPainter painter(&pmScaled); painter.setPen(color); painter.setFont(font); painter.drawText(0 + 1, 0, width, height, Qt::AlignHCenter | Qt::AlignVCenter, text); painter.end(); QPixmapCache::insert(key, pmScaled); return pmScaled; } QPixmap FoundCountIcon::foundCountPixmap(bool isLoading, int countFound, bool childrenAreLoading, int countChildsFound, const QFont &font, int height) const { if (isLoading) return QPixmap(); QFont boldFont(font); boldFont.setBold(true); QString text; if (childrenAreLoading) { if (countChildsFound > 0) text = i18n("%1+%2+", QString::number(countFound), QString::number(countChildsFound)); else text = i18n("%1+", QString::number(countFound)); } else { if (countChildsFound > 0) text = i18n("%1+%2", QString::number(countFound), QString::number(countChildsFound)); else if (countFound > 0) text = QString::number(countFound); else return QPixmap(); } return circledTextPixmap(text, height, boldFont, m_basketTree->palette().color(QPalette::HighlightedText)); } diff --git a/src/basketlistview.h b/src/basketlistview.h index ddfbc71..12c137f 100644 --- a/src/basketlistview.h +++ b/src/basketlistview.h @@ -1,144 +1,130 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef BASKETLISTVIEW_H #define BASKETLISTVIEW_H #include #include #include class QPixmap; class QResizeEvent; class QDragEnterEvent; class QDropEvent; class QDragMoveEvent; class QFocusEvent; class QDragLeaveEvent; class BasketScene; class BasketListViewItem : public QTreeWidgetItem { public: /// CONSTRUCTOR AND DESTRUCTOR: BasketListViewItem(QTreeWidget *parent, BasketScene *basket); BasketListViewItem(QTreeWidgetItem *parent, BasketScene *basket); BasketListViewItem(QTreeWidget *parent, QTreeWidgetItem *after, BasketScene *basket); BasketListViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, BasketScene *basket); ~BasketListViewItem() override; BasketScene *basket() { return m_basket; } void setup(); BasketListViewItem *lastChild(); QStringList childNamesTree(int deep = 0); void moveChildsBaskets(); void ensureVisible(); bool isShown(); bool isCurrentBasket(); bool isUnderDrag(); QString escapedName(const QString &string); bool haveChildsLoading(); bool haveHiddenChildsLoading(); bool haveChildsLocked(); bool haveHiddenChildsLocked(); int countChildsFound(); int countHiddenChildsFound(); void setUnderDrag(bool); bool isAbbreviated(); void setAbbreviated(bool b); private: BasketScene *m_basket; int m_width; bool m_isUnderDrag; bool m_isAbbreviated; }; Q_DECLARE_METATYPE(BasketListViewItem *); class BasketTreeListView : public QTreeWidget { Q_OBJECT public: explicit BasketTreeListView(QWidget *parent = nullptr); void dragEnterEvent(QDragEnterEvent *event) override; void removeExpands(); void dragLeaveEvent(QDragLeaveEvent *event) override; void dragMoveEvent(QDragMoveEvent *event) override; void dropEvent(QDropEvent *event) override; void resizeEvent(QResizeEvent *event) override; void contextMenuEvent(QContextMenuEvent *event) override; Qt::DropActions supportedDropActions() const override; /*! Retrieve a basket from the tree * @see BasketListViewItem::basket() */ BasketListViewItem *getBasketInTree(const QModelIndex &index) const; static QString TREE_ITEM_MIME_STRING; protected: QStringList mimeTypes() const override; QMimeData *mimeData(const QList items) const override; bool event(QEvent *e) override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void focusInEvent(QFocusEvent *) override; private: QTimer m_autoOpenTimer; QTreeWidgetItem *m_autoOpenItem; signals: void contextMenuRequested(const QPoint &); private slots: void autoOpen(); private: void setItemUnderDrag(BasketListViewItem *item); BasketListViewItem *m_itemUnderDrag; QPoint m_dragStartPosition; }; /** @class FoundCountIcon * Custom-drawn "little numbers" shown on Filter all */ class FoundCountIcon : public QStyledItemDelegate { public: FoundCountIcon(BasketTreeListView *basketTree, QObject *parent = NULL) : QStyledItemDelegate(parent) , m_basketTree(basketTree) { } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; private: QPixmap circledTextPixmap(const QString &text, int height, const QFont &font, const QColor &color) const; //! @returns Rect-with-number, or null pixmap if nothing was found / basket is loading QPixmap foundCountPixmap(bool isLoading, int countFound, bool childrenAreLoading, int countChildsFound, const QFont &font, int height) const; BasketTreeListView *m_basketTree; }; #endif // BASKETLISTVIEW_H diff --git a/src/basketproperties.cpp b/src/basketproperties.cpp index c9c666d..44fc6d8 100644 --- a/src/basketproperties.cpp +++ b/src/basketproperties.cpp @@ -1,229 +1,215 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "basketproperties.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "backgroundmanager.h" #include "basketscene.h" #include "gitwrapper.h" #include "global.h" #include "kcolorcombo2.h" #include "variouswidgets.h" #include "ui_basketproperties.h" BasketPropertiesDialog::BasketPropertiesDialog(BasketScene *basket, QWidget *parent) : QDialog(parent) , Ui::BasketPropertiesUi() , m_basket(basket) { // Set up dialog options setWindowTitle(i18n("Basket Properties")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply, this); QWidget *mainWidget = new QWidget(this); setupUi(mainWidget); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); okButton->setDefault(true); setObjectName("BasketProperties"); setModal(true); Ui::BasketPropertiesUi *propsUi = dynamic_cast(this); // cast to remove name ambiguity propsUi->icon->setIconType(KIconLoader::NoGroup, KIconLoader::Action); propsUi->icon->setIconSize(16); propsUi->icon->setIcon(m_basket->icon()); int size = qMax(propsUi->icon->sizeHint().width(), propsUi->icon->sizeHint().height()); propsUi->icon->setFixedSize(size, size); // Make it square! propsUi->icon->setToolTip(i18n("Icon")); propsUi->name->setText(m_basket->basketName()); propsUi->name->setMinimumWidth(propsUi->name->fontMetrics().maxWidth() * 20); propsUi->name->setToolTip(i18n("Name")); // Appearance: m_backgroundColor = new KColorCombo2(m_basket->backgroundColorSetting(), palette().color(QPalette::Base), appearanceGroup); m_textColor = new KColorCombo2(m_basket->textColorSetting(), palette().color(QPalette::Text), appearanceGroup); bgColorLbl->setBuddy(m_backgroundColor); txtColorLbl->setBuddy(m_textColor); appearanceLayout->addWidget(m_backgroundColor, 1, 2); appearanceLayout->addWidget(m_textColor, 2, 2); setTabOrder(backgroundImage, m_backgroundColor); setTabOrder(m_backgroundColor, m_textColor); setTabOrder(m_textColor, columnForm); backgroundImage->addItem(i18n("(None)")); m_backgroundImagesMap.insert(0, ""); backgroundImage->setIconSize(QSize(100, 75)); QStringList backgrounds = Global::backgroundManager->imageNames(); int index = 1; for (QStringList::Iterator it = backgrounds.begin(); it != backgrounds.end(); ++it) { QPixmap *preview = Global::backgroundManager->preview(*it); if (preview) { m_backgroundImagesMap.insert(index, *it); backgroundImage->insertItem(index, *it); backgroundImage->setItemData(index, *preview, Qt::DecorationRole); if (m_basket->backgroundImageName() == *it) backgroundImage->setCurrentIndex(index); index++; } } // m_backgroundImage->insertItem(i18n("Other..."), -1); int BUTTON_MARGIN = qApp->style()->pixelMetric(QStyle::PM_ButtonMargin); backgroundImage->setMaxVisibleItems(50 /*75 * 6 / m_backgroundImage->sizeHint().height()*/); backgroundImage->setMinimumHeight(75 + 2 * BUTTON_MARGIN); // Disposition: columnCount->setRange(1, 20); columnCount->setValue(m_basket->columnsCount()); connect(columnCount, SIGNAL(valueChanged(int)), this, SLOT(selectColumnsLayout())); int height = qMax(mindMap->sizeHint().height(), columnCount->sizeHint().height()); // Make all radioButtons vertically equally-spaced! mindMap->setMinimumSize(mindMap->sizeHint().width(), height); // Because the m_columnCount can be higher, and make radio1 and radio2 more spaced than radio2 and radio3. if (!m_basket->isFreeLayout()) columnForm->setChecked(true); else if (m_basket->isMindMap()) mindMap->setChecked(true); else freeForm->setChecked(true); mindMap->hide(); // Keyboard Shortcut: QList shortcuts {m_basket->shortcut()}; shortcut->setShortcut(shortcuts); HelpLabel *helpLabel = new HelpLabel(i18n("Learn some tips..."), i18n("

Easily Remember your Shortcuts:
" "With the first option, giving the basket a shortcut of the form Alt+Letter will underline that letter in the basket tree.
" "For instance, if you are assigning the shortcut Alt+T to a basket named Tips, the basket will be displayed as Tips in the tree. " "It helps you visualize the shortcuts to remember them more quickly.

" "

Local vs Global:
" "The first option allows you to show the basket while the main window is active. " "Global shortcuts are valid from anywhere, even if the window is hidden.

" "

Show vs Switch:
" "The last option makes this basket the current one without opening the main window. " "It is useful in addition to the configurable global shortcuts, eg. to paste the clipboard or the selection into the current basket from anywhere.

"), 0); shortcutLayout->addWidget(helpLabel); connect(shortcut, SIGNAL(shortcutChanged(const QList &)), this, SLOT(capturedShortcut(const QList &))); setTabOrder(columnCount, shortcut); setTabOrder(shortcut, helpLabel); setTabOrder(helpLabel, showBasket); switch (m_basket->shortcutAction()) { default: case 0: showBasket->setChecked(true); break; case 1: globalButton->setChecked(true); break; case 2: switchButton->setChecked(true); break; } // Connect the Ok and Apply buttons to actually apply the changes connect(okButton, SIGNAL(clicked()), SLOT(applyChanges())); connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), SLOT(applyChanges())); } BasketPropertiesDialog::~BasketPropertiesDialog() { } void BasketPropertiesDialog::ensurePolished() { ensurePolished(); Ui::BasketPropertiesUi *propsUi = dynamic_cast(this); propsUi->name->setFocus(); } void BasketPropertiesDialog::applyChanges() { if (columnForm->isChecked()) { m_basket->setDisposition(0, columnCount->value()); } else if (freeForm->isChecked()) { m_basket->setDisposition(1, columnCount->value()); } else { m_basket->setDisposition(2, columnCount->value()); } if (showBasket->isChecked()) { m_basket->setShortcut(shortcut->shortcut()[0], 0); } else if (globalButton->isChecked()) { m_basket->setShortcut(shortcut->shortcut()[0], 1); } else if (switchButton->isChecked()) { m_basket->setShortcut(shortcut->shortcut()[0], 2); } Ui::BasketPropertiesUi *propsUi = dynamic_cast(this); // Should be called LAST, because it will emit the propertiesChanged() signal and the tree will be able to show the newly set Alt+Letter shortcut: m_basket->setAppearance(propsUi->icon->icon(), propsUi->name->text(), m_backgroundImagesMap[backgroundImage->currentIndex()], m_backgroundColor->color(), m_textColor->color()); GitWrapper::commitBasket(m_basket); m_basket->save(); } void BasketPropertiesDialog::capturedShortcut(const QList &sc) { // TODO: Validate it! shortcut->setShortcut(sc); } void BasketPropertiesDialog::selectColumnsLayout() { columnForm->setChecked(true); } diff --git a/src/basketproperties.h b/src/basketproperties.h index 3ed2400..a0257eb 100644 --- a/src/basketproperties.h +++ b/src/basketproperties.h @@ -1,69 +1,55 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef BASKETPROPERTIES_H #define BASKETPROPERTIES_H #include #include #include #include "ui_basketproperties.h" class KIconButton; class QLineEdit; class QGroupBox; class QVBoxLayout; class QRadioButton; class QString; class KComboBox; class KShortcutWidget; class QKeySequence; class KColorCombo2; class BasketScene; /** The dialog that hold basket settings. * @author Sébastien Laoût */ class BasketPropertiesDialog : public QDialog, private Ui::BasketPropertiesUi { Q_OBJECT public: explicit BasketPropertiesDialog(BasketScene *basket, QWidget *parent = 0); ~BasketPropertiesDialog() override; void ensurePolished(); public slots: void applyChanges(); protected slots: void capturedShortcut(const QList &shortcut); void selectColumnsLayout(); private: BasketScene *m_basket; KColorCombo2 *m_backgroundColor; KColorCombo2 *m_textColor; QMap m_backgroundImagesMap; }; #endif // BASKETPROPERTIES_H diff --git a/src/basketscene.cpp b/src/basketscene.cpp index 15dffce..0d070a9 100644 --- a/src/basketscene.cpp +++ b/src/basketscene.cpp @@ -1,5295 +1,5281 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "basketscene.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // seed for rand() #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for KStatefulBrush #include #include #include #include #include #include #include #include #include #include // rand() function #include "backgroundmanager.h" #include "basketview.h" #include "debugwindow.h" #include "decoratedbasket.h" #include "diskerrordialog.h" #include "focusedwidgets.h" #include "gitwrapper.h" #include "global.h" #include "note.h" #include "notedrag.h" #include "noteedit.h" #include "notefactory.h" #include "noteselection.h" #include "settings.h" #include "tagsedit.h" #include "tools.h" #include "transparentwidget.h" #include "xmlwork.h" #include "config.h" #ifdef HAVE_LIBGPGME #include "kgpgme.h" #endif const int BasketScene::ANIMATION_DELAY = 2000; void debugZone(int zone) { QString s; switch (zone) { case Note::Handle: s = "Handle"; break; case Note::Group: s = "Group"; break; case Note::TagsArrow: s = "TagsArrow"; break; case Note::Custom0: s = "Custom0"; break; case Note::GroupExpander: s = "GroupExpander"; break; case Note::Content: s = "Content"; break; case Note::Link: s = "Link"; break; case Note::TopInsert: s = "TopInsert"; break; case Note::TopGroup: s = "TopGroup"; break; case Note::BottomInsert: s = "BottomInsert"; break; case Note::BottomGroup: s = "BottomGroup"; break; case Note::BottomColumn: s = "BottomColumn"; break; case Note::None: s = "None"; break; default: if (zone == Note::Emblem0) s = "Emblem0"; else s = "Emblem0+" + QString::number(zone - Note::Emblem0); break; } qDebug() << s; } #define FOR_EACH_NOTE(noteVar) for (Note *noteVar = firstNote(); noteVar; noteVar = noteVar->next()) void BasketScene::prependNoteIn(Note *note, Note *in) { if (!note) // No note to prepend: return; if (in) { // The normal case: preparePlug(note); Note *last = note->lastSibling(); for (Note *n = note; n; n = n->next()) n->setParentNote(in); // note->setPrev(0L); last->setNext(in->firstChild()); if (in->firstChild()) in->firstChild()->setPrev(last); in->setFirstChild(note); if (m_loaded) signalCountsChanged(); } else // Prepend it directly in the basket: appendNoteBefore(note, firstNote()); } void BasketScene::appendNoteIn(Note *note, Note *in) { if (!note) // No note to append: return; if (in) { // The normal case: preparePlug(note); // Note *last = note->lastSibling(); Note *lastChild = in->lastChild(); for (Note *n = note; n; n = n->next()) n->setParentNote(in); note->setPrev(lastChild); // last->setNext(0L); if (!in->firstChild()) in->setFirstChild(note); if (lastChild) lastChild->setNext(note); if (m_loaded) signalCountsChanged(); } else // Prepend it directly in the basket: appendNoteAfter(note, lastNote()); } void BasketScene::appendNoteAfter(Note *note, Note *after) { if (!note) // No note to append: return; if (!after) // By default, insert after the last note: after = lastNote(); if (m_loaded && after && !after->isFree() && !after->isColumn()) for (Note *n = note; n; n = n->next()) n->inheritTagsOf(after); // if (!alreadyInBasket) preparePlug(note); Note *last = note->lastSibling(); if (after) { // The normal case: for (Note *n = note; n; n = n->next()) n->setParentNote(after->parentNote()); note->setPrev(after); last->setNext(after->next()); after->setNext(note); if (last->next()) last->next()->setPrev(last); } else { // There is no note in the basket: for (Note *n = note; n; n = n->next()) n->setParentNote(0); m_firstNote = note; // note->setPrev(0); // last->setNext(0); } // if (!alreadyInBasket) if (m_loaded) signalCountsChanged(); } void BasketScene::appendNoteBefore(Note *note, Note *before) { if (!note) // No note to append: return; if (!before) // By default, insert before the first note: before = firstNote(); if (m_loaded && before && !before->isFree() && !before->isColumn()) for (Note *n = note; n; n = n->next()) n->inheritTagsOf(before); preparePlug(note); Note *last = note->lastSibling(); if (before) { // The normal case: for (Note *n = note; n; n = n->next()) n->setParentNote(before->parentNote()); note->setPrev(before->prev()); last->setNext(before); before->setPrev(last); if (note->prev()) note->prev()->setNext(note); else { if (note->parentNote()) note->parentNote()->setFirstChild(note); else m_firstNote = note; } } else { // There is no note in the basket: for (Note *n = note; n; n = n->next()) n->setParentNote(0); m_firstNote = note; // note->setPrev(0); // last->setNext(0); } if (m_loaded) signalCountsChanged(); } DecoratedBasket *BasketScene::decoration() { return (DecoratedBasket *)parent(); } void BasketScene::preparePlug(Note *note) { // Select only the new notes, compute the new notes count and the new number of found notes: if (m_loaded) unselectAll(); int count = 0; int founds = 0; Note *last = 0; for (Note *n = note; n; n = n->next()) { if (m_loaded) n->setSelectedRecursively(true); // Notes should have a parent basket (and they have, so that's OK). count += n->count(); founds += n->newFilter(decoration()->filterData()); last = n; } m_count += count; m_countFounds += founds; // Focus the last inserted note: if (m_loaded && last) { setFocusedNote(last); m_startOfShiftSelectionNote = (last->isGroup() ? last->lastRealChild() : last); } // If some notes don't match (are hidden), tell it to the user: if (m_loaded && founds < count) { if (count == 1) postMessage(i18n("The new note does not match the filter and is hidden.")); else if (founds == count - 1) postMessage(i18n("A new note does not match the filter and is hidden.")); else if (founds > 0) postMessage(i18n("Some new notes do not match the filter and are hidden.")); else postMessage(i18n("The new notes do not match the filter and are hidden.")); } } void BasketScene::unplugNote(Note *note) { // If there is nothing to do... if (!note) return; // if (!willBeReplugged) { note->setSelectedRecursively(false); // To removeSelectedNote() and decrease the selectedsCount. m_count -= note->count(); m_countFounds -= note->newFilter(decoration()->filterData()); signalCountsChanged(); // } // If it was the first note, change the first note: if (m_firstNote == note) m_firstNote = note->next(); // Change previous and next notes: if (note->prev()) note->prev()->setNext(note->next()); if (note->next()) note->next()->setPrev(note->prev()); if (note->parentNote()) { // If it was the first note of a group, change the first note of the group: if (note->parentNote()->firstChild() == note) note->parentNote()->setFirstChild(note->next()); if (!note->parentNote()->isColumn()) { // Delete parent if now 0 notes inside parent group: if (!note->parentNote()->firstChild()) { unplugNote(note->parentNote()); // a group could call this method for one or more of its children, // each children could call this method for its parent's group... // we have to do the deletion later otherwise we may corrupt the current process m_notesToBeDeleted << note; if (m_notesToBeDeleted.count() == 1) { QTimer::singleShot(0, this, SLOT(doCleanUp())); } } // Ungroup if still 1 note inside parent group: else if (!note->parentNote()->firstChild()->next()) { ungroupNote(note->parentNote()); } } } note->setParentNote(0); note->setPrev(0); note->setNext(0); // Reste focus and hover note if necessary if (m_focusedNote == note) m_focusedNote = 0; if (m_hoveredNote == note) m_hoveredNote = 0; // recomputeBlankRects(); // FIXME: called too much time. It's here because when dragging and moving a note to another basket and then go back to the original basket, the note is deleted but the note rect is not painter anymore. } void BasketScene::ungroupNote(Note *group) { Note *note = group->firstChild(); Note *lastGroupedNote = group; Note *nextNote; // Move all notes after the group (not before, to avoid to change m_firstNote or group->m_firstChild): while (note) { nextNote = note->next(); if (lastGroupedNote->next()) lastGroupedNote->next()->setPrev(note); note->setNext(lastGroupedNote->next()); lastGroupedNote->setNext(note); note->setParentNote(group->parentNote()); note->setPrev(lastGroupedNote); note->setGroupWidth(group->groupWidth() - Note::GROUP_WIDTH); lastGroupedNote = note; note = nextNote; } // Unplug the group: group->setFirstChild(0); unplugNote(group); // a group could call this method for one or more of its children, // each children could call this method for its parent's group... // we have to do the deletion later otherwise we may corrupt the current process m_notesToBeDeleted << group; if (m_notesToBeDeleted.count() == 1) { QTimer::singleShot(0, this, SLOT(doCleanUp())); } } void BasketScene::groupNoteBefore(Note *note, Note *with) { if (!note || !with) // No note to group or nowhere to group it: return; // if (m_loaded && before && !with->isFree() && !with->isColumn()) for (Note *n = note; n; n = n->next()) n->inheritTagsOf(with); preparePlug(note); Note *last = note->lastSibling(); Note *group = new Note(this); group->setPrev(with->prev()); group->setNext(with->next()); group->setX(with->x()); group->setY(with->y()); if (with->parentNote() && with->parentNote()->firstChild() == with) with->parentNote()->setFirstChild(group); else if (m_firstNote == with) m_firstNote = group; group->setParentNote(with->parentNote()); group->setFirstChild(note); group->setGroupWidth(with->groupWidth() + Note::GROUP_WIDTH); if (with->prev()) with->prev()->setNext(group); if (with->next()) with->next()->setPrev(group); with->setParentNote(group); with->setPrev(last); with->setNext(0L); for (Note *n = note; n; n = n->next()) n->setParentNote(group); // note->setPrev(0L); last->setNext(with); if (m_loaded) signalCountsChanged(); } void BasketScene::groupNoteAfter(Note *note, Note *with) { if (!note || !with) // No note to group or nowhere to group it: return; // if (m_loaded && before && !with->isFree() && !with->isColumn()) for (Note *n = note; n; n = n->next()) n->inheritTagsOf(with); preparePlug(note); // Note *last = note->lastSibling(); Note *group = new Note(this); group->setPrev(with->prev()); group->setNext(with->next()); group->setX(with->x()); group->setY(with->y()); if (with->parentNote() && with->parentNote()->firstChild() == with) with->parentNote()->setFirstChild(group); else if (m_firstNote == with) m_firstNote = group; group->setParentNote(with->parentNote()); group->setFirstChild(with); group->setGroupWidth(with->groupWidth() + Note::GROUP_WIDTH); if (with->prev()) with->prev()->setNext(group); if (with->next()) with->next()->setPrev(group); with->setParentNote(group); with->setPrev(0L); with->setNext(note); for (Note *n = note; n; n = n->next()) n->setParentNote(group); note->setPrev(with); // last->setNext(0L); if (m_loaded) signalCountsChanged(); } void BasketScene::doCleanUp() { QSet::iterator it = m_notesToBeDeleted.begin(); while (it != m_notesToBeDeleted.end()) { delete *it; it = m_notesToBeDeleted.erase(it); } } void BasketScene::loadNotes(const QDomElement ¬es, Note *parent) { Note *note; for (QDomNode n = notes.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement e = n.toElement(); if (e.isNull()) // Cannot handle that! continue; note = 0; // Load a Group: if (e.tagName() == "group") { note = new Note(this); // 1. Create the group... loadNotes(e, note); // 3. ... And populate it with child notes. int noteCount = note->count(); if (noteCount > 0 || (parent == 0 && !isFreeLayout())) { // But don't remove columns! appendNoteIn(note, parent); // 2. ... Insert it... FIXME: Initially, the if() the insertion was the step 2. Was it on purpose? // The notes in the group are counted two times (it's why appendNoteIn() was called before loadNotes): m_count -= noteCount; // TODO: Recompute note count every time noteCount() is emitted! m_countFounds -= noteCount; } } // Load a Content-Based Note: if (e.tagName() == "note" || e.tagName() == "item") { // Keep compatible with 0.6.0 Alpha 1 note = new Note(this); // Create the note... NoteFactory__loadNode(XMLWork::getElement(e, "content"), e.attribute("type"), note, /*lazyLoad=*/m_finishLoadOnFirstShow); // ... Populate it with content... if (e.attribute("type") == "text") m_shouldConvertPlainTextNotes = true; // Convert Pre-0.6.0 baskets: plain text notes should be converted to rich text ones once all is loaded! appendNoteIn(note, parent); // ... And insert it. // Load dates: if (e.hasAttribute("added")) note->setAddedDate(QDateTime::fromString(e.attribute("added"), Qt::ISODate)); if (e.hasAttribute("lastModification")) note->setLastModificationDate(QDateTime::fromString(e.attribute("lastModification"), Qt::ISODate)); } // If we successfully loaded a note: if (note) { // Free Note Properties: if (note->isFree()) { int x = e.attribute("x").toInt(); int y = e.attribute("y").toInt(); note->setX(x < 0 ? 0 : x); note->setY(y < 0 ? 0 : y); } // Resizeable Note Properties: if (note->hasResizer() || note->isColumn()) note->setGroupWidth(e.attribute("width", "200").toInt()); // Group Properties: if (note->isGroup() && !note->isColumn() && XMLWork::trueOrFalse(e.attribute("folded", "false"))) note->toggleFolded(); // Tags: if (note->content()) { QString tagsString = XMLWork::getElementText(e, "tags", ""); QStringList tagsId = tagsString.split(';'); for (QStringList::iterator it = tagsId.begin(); it != tagsId.end(); ++it) { State *state = Tag::stateForId(*it); if (state) note->addState(state, /*orReplace=*/true); } } } qApp->processEvents(); } } void BasketScene::saveNotes(QXmlStreamWriter &stream, Note *parent) { Note *note = (parent ? parent->firstChild() : firstNote()); while (note) { // Create Element: stream.writeStartElement(note->isGroup() ? "group" : "note"); // Free Note Properties: if (note->isFree()) { stream.writeAttribute("x", QString::number(note->x())); stream.writeAttribute("y", QString::number(note->y())); } // Resizeable Note Properties: if (note->hasResizer()) stream.writeAttribute("width", QString::number(note->groupWidth())); // Group Properties: if (note->isGroup() && !note->isColumn()) stream.writeAttribute("folded", XMLWork::trueOrFalse(note->isFolded())); // Save Content: if (note->content()) { // Save Dates: stream.writeAttribute("added", note->addedDate().toString(Qt::ISODate)); stream.writeAttribute("lastModification", note->lastModificationDate().toString(Qt::ISODate)); // Save Content: stream.writeAttribute("type", note->content()->lowerTypeName()); note->content()->saveToNode(stream); // Save Tags: if (note->states().count() > 0) { QString tags; for (State::List::iterator it = note->states().begin(); it != note->states().end(); ++it) tags += (tags.isEmpty() ? "" : ";") + (*it)->id(); stream.writeTextElement("tags", tags); } } else { // Save Child Notes: saveNotes(stream, note); } stream.writeEndElement(); // Go to the Next One: note = note->next(); } } void BasketScene::loadProperties(const QDomElement &properties) { // Compute Default Values for When Loading the Properties: QString defaultBackgroundColor = (backgroundColorSetting().isValid() ? backgroundColorSetting().name() : ""); QString defaultTextColor = (textColorSetting().isValid() ? textColorSetting().name() : ""); // Load the Properties: QString icon = XMLWork::getElementText(properties, "icon", this->icon()); QString name = XMLWork::getElementText(properties, "name", basketName()); QDomElement appearance = XMLWork::getElement(properties, "appearance"); // In 0.6.0-Alpha versions, there was a typo error: "backround" instead of "background" QString backgroundImage = appearance.attribute("backgroundImage", appearance.attribute("backroundImage", backgroundImageName())); QString backgroundColorString = appearance.attribute("backgroundColor", appearance.attribute("backroundColor", defaultBackgroundColor)); QString textColorString = appearance.attribute("textColor", defaultTextColor); QColor backgroundColor = (backgroundColorString.isEmpty() ? QColor() : QColor(backgroundColorString)); QColor textColor = (textColorString.isEmpty() ? QColor() : QColor(textColorString)); QDomElement disposition = XMLWork::getElement(properties, "disposition"); bool free = XMLWork::trueOrFalse(disposition.attribute("free", XMLWork::trueOrFalse(isFreeLayout()))); int columnCount = disposition.attribute("columnCount", QString::number(this->columnsCount())).toInt(); bool mindMap = XMLWork::trueOrFalse(disposition.attribute("mindMap", XMLWork::trueOrFalse(isMindMap()))); QDomElement shortcut = XMLWork::getElement(properties, "shortcut"); QString actionStrings[] = {"show", "globalShow", "globalSwitch"}; QKeySequence combination = QKeySequence(shortcut.attribute("combination", m_action->shortcut().toString())); QString actionString = shortcut.attribute("action"); int action = shortcutAction(); if (actionString == actionStrings[0]) action = 0; if (actionString == actionStrings[1]) action = 1; if (actionString == actionStrings[2]) action = 2; QDomElement protection = XMLWork::getElement(properties, "protection"); m_encryptionType = protection.attribute("type").toInt(); m_encryptionKey = protection.attribute("key"); // Apply the Properties: setDisposition((free ? (mindMap ? 2 : 1) : 0), columnCount); setShortcut(combination, action); setAppearance(icon, name, backgroundImage, backgroundColor, textColor); // Will emit propertiesChanged(this) } void BasketScene::saveProperties(QXmlStreamWriter &stream) { stream.writeStartElement("properties"); stream.writeTextElement("name", basketName()); stream.writeTextElement("icon", icon()); stream.writeStartElement("appearance"); stream.writeAttribute("backgroundColor", backgroundColorSetting().isValid() ? backgroundColorSetting().name() : ""); stream.writeAttribute("backgroundImage", backgroundImageName()); stream.writeAttribute("textColor", textColorSetting().isValid() ? textColorSetting().name() : ""); stream.writeEndElement(); stream.writeStartElement("disposition"); stream.writeAttribute("columnCount", QString::number(columnsCount())); stream.writeAttribute("free", XMLWork::trueOrFalse(isFreeLayout())); stream.writeAttribute("mindMap", XMLWork::trueOrFalse(isMindMap())); stream.writeEndElement(); stream.writeStartElement("shortcut"); QString actionStrings[] = {"show", "globalShow", "globalSwitch"}; stream.writeAttribute("action", actionStrings[shortcutAction()]); stream.writeAttribute("combination", m_action->shortcut().toString()); stream.writeEndElement(); stream.writeStartElement("protection"); stream.writeAttribute("key", m_encryptionKey); stream.writeAttribute("type", QString::number(m_encryptionType)); stream.writeEndElement(); stream.writeEndElement(); } void BasketScene::subscribeBackgroundImages() { if (!m_backgroundImageName.isEmpty()) { Global::backgroundManager->subscribe(m_backgroundImageName); Global::backgroundManager->subscribe(m_backgroundImageName, this->backgroundColor()); Global::backgroundManager->subscribe(m_backgroundImageName, selectionRectInsideColor()); m_backgroundPixmap = Global::backgroundManager->pixmap(m_backgroundImageName); m_opaqueBackgroundPixmap = Global::backgroundManager->opaquePixmap(m_backgroundImageName, this->backgroundColor()); m_selectedBackgroundPixmap = Global::backgroundManager->opaquePixmap(m_backgroundImageName, selectionRectInsideColor()); m_backgroundTiled = Global::backgroundManager->tiled(m_backgroundImageName); } } void BasketScene::unsubscribeBackgroundImages() { if (hasBackgroundImage()) { Global::backgroundManager->unsubscribe(m_backgroundImageName); Global::backgroundManager->unsubscribe(m_backgroundImageName, this->backgroundColor()); Global::backgroundManager->unsubscribe(m_backgroundImageName, selectionRectInsideColor()); m_backgroundPixmap = 0; m_opaqueBackgroundPixmap = 0; m_selectedBackgroundPixmap = 0; } } void BasketScene::setAppearance(const QString &icon, const QString &name, const QString &backgroundImage, const QColor &backgroundColor, const QColor &textColor) { unsubscribeBackgroundImages(); m_icon = icon; m_basketName = name; m_backgroundImageName = backgroundImage; m_backgroundColorSetting = backgroundColor; m_textColorSetting = textColor; // Where is this shown? m_action->setText("BASKET SHORTCUT: " + name); // Basket should ALWAYS have an icon (the "basket" icon by default): QPixmap iconTest = KIconLoader::global()->loadIcon(m_icon, KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, /*canReturnNull=*/true); if (iconTest.isNull()) m_icon = "basket"; // We don't request the background images if it's not loaded yet (to make the application startup fast). // When the basket is loading (because requested by the user: he/she want to access it) // it load the properties, subscribe to (and then load) the images, update the "Loading..." message with the image, // load all the notes and it's done! if (m_loadingLaunched) subscribeBackgroundImages(); recomputeAllStyles(); // If a note have a tag with the same background color as the basket one, then display a "..." recomputeBlankRects(); // See the drawing of blank areas in BasketScene::drawContents() unbufferizeAll(); if (isDuringEdit() && m_editor->graphicsWidget()) { QPalette palette; palette.setColor(m_editor->graphicsWidget()->widget()->backgroundRole(), m_editor->note()->backgroundColor()); palette.setColor(m_editor->graphicsWidget()->widget()->foregroundRole(), m_editor->note()->textColor()); m_editor->graphicsWidget()->setPalette(palette); } emit propertiesChanged(this); } void BasketScene::setDisposition(int disposition, int columnCount) { static const int COLUMNS_LAYOUT = 0; static const int FREE_LAYOUT = 1; static const int MINDMAPS_LAYOUT = 2; int currentDisposition = (isFreeLayout() ? (isMindMap() ? MINDMAPS_LAYOUT : FREE_LAYOUT) : COLUMNS_LAYOUT); if (currentDisposition == COLUMNS_LAYOUT && disposition == COLUMNS_LAYOUT) { if (firstNote() && columnCount > m_columnsCount) { // Insert each new columns: for (int i = m_columnsCount; i < columnCount; ++i) { Note *newColumn = new Note(this); insertNote(newColumn, /*clicked=*/lastNote(), /*zone=*/Note::BottomInsert, QPointF(), /*animateNewPosition=*/false); } } else if (firstNote() && columnCount < m_columnsCount) { Note *column = firstNote(); Note *cuttedNotes = 0; for (int i = 1; i <= m_columnsCount; ++i) { Note *columnToRemove = column; column = column->next(); if (i > columnCount) { // Remove the columns that are too much: unplugNote(columnToRemove); // "Cut" the content in the columns to be deleted: if (columnToRemove->firstChild()) { for (Note *it = columnToRemove->firstChild(); it; it = it->next()) it->setParentNote(0); if (!cuttedNotes) cuttedNotes = columnToRemove->firstChild(); else { Note *lastCuttedNote = cuttedNotes; while (lastCuttedNote->next()) lastCuttedNote = lastCuttedNote->next(); lastCuttedNote->setNext(columnToRemove->firstChild()); columnToRemove->firstChild()->setPrev(lastCuttedNote); } columnToRemove->setFirstChild(0); } delete columnToRemove; } } // Paste the content in the last column: if (cuttedNotes) insertNote(cuttedNotes, /*clicked=*/lastNote(), /*zone=*/Note::BottomColumn, QPointF(), /*animateNewPosition=*/true); unselectAll(); } if (columnCount != m_columnsCount) { m_columnsCount = (columnCount <= 0 ? 1 : columnCount); equalizeColumnSizes(); // Will relayoutNotes() } } else if (currentDisposition == COLUMNS_LAYOUT && (disposition == FREE_LAYOUT || disposition == MINDMAPS_LAYOUT)) { Note *column = firstNote(); m_columnsCount = 0; // Now, so relayoutNotes() will not relayout the free notes as if they were columns! while (column) { // Move all childs on the first level: Note *nextColumn = column->next(); ungroupNote(column); column = nextColumn; } unselectAll(); m_mindMap = (disposition == MINDMAPS_LAYOUT); relayoutNotes(true); } else if ((currentDisposition == FREE_LAYOUT || currentDisposition == MINDMAPS_LAYOUT) && disposition == COLUMNS_LAYOUT) { if (firstNote()) { // TODO: Reorder notes! // Remove all notes (but keep a reference to them, we're not crazy ;-) ): Note *notes = m_firstNote; m_firstNote = 0; m_count = 0; m_countFounds = 0; // Insert the number of columns that is needed: Note *lastInsertedColumn = 0; for (int i = 0; i < columnCount; ++i) { Note *column = new Note(this); if (lastInsertedColumn) insertNote(column, /*clicked=*/lastInsertedColumn, /*zone=*/Note::BottomInsert, QPointF(), /*animateNewPosition=*/false); else m_firstNote = column; lastInsertedColumn = column; } // Reinsert the old notes in the first column: insertNote(notes, /*clicked=*/firstNote(), /*zone=*/Note::BottomColumn, QPointF(), /*animateNewPosition=*/true); unselectAll(); } else { // Insert the number of columns that is needed: Note *lastInsertedColumn = 0; for (int i = 0; i < columnCount; ++i) { Note *column = new Note(this); if (lastInsertedColumn) insertNote(column, /*clicked=*/lastInsertedColumn, /*zone=*/Note::BottomInsert, QPointF(), /*animateNewPosition=*/false); else m_firstNote = column; lastInsertedColumn = column; } } m_columnsCount = (columnCount <= 0 ? 1 : columnCount); equalizeColumnSizes(); // Will relayoutNotes() } } void BasketScene::equalizeColumnSizes() { if (!firstNote()) return; // Necessary to know the available space; relayoutNotes(true); int availableSpace = m_view->viewport()->width(); int columnWidth = (availableSpace - (columnsCount() - 1) * Note::GROUP_WIDTH) / columnsCount(); int columnCount = columnsCount(); Note *column = firstNote(); while (column) { int minGroupWidth = column->minRight() - column->x(); if (minGroupWidth > columnWidth) { availableSpace -= minGroupWidth; --columnCount; } column = column->next(); } columnWidth = (availableSpace - (columnsCount() - 1) * Note::GROUP_WIDTH) / columnCount; column = firstNote(); while (column) { int minGroupWidth = column->minRight() - column->x(); if (minGroupWidth > columnWidth) column->setGroupWidth(minGroupWidth); else column->setGroupWidth(columnWidth); column = column->next(); } relayoutNotes(true); } void BasketScene::enableActions() { Global::bnpView->enableActions(); m_view->setFocusPolicy(isLocked() ? Qt::NoFocus : Qt::StrongFocus); if (isLocked()) m_view->viewport()->setCursor(Qt::ArrowCursor); // When locking, the cursor stays the last form it was } bool BasketScene::save() { if (!m_loaded) return false; DEBUG_WIN << "Basket[" + folderName() + "]: Saving..."; QString data; QXmlStreamWriter stream(&data); XMLWork::setupXmlStream(stream, "basket"); // Create Properties Element and Populate It: saveProperties(stream); // Create Notes Element and Populate It: stream.writeStartElement("notes"); saveNotes(stream, NULL); stream.writeEndElement(); stream.writeEndElement(); stream.writeEndDocument(); // Write to Disk: if (!saveToFile(fullPath() + ".basket", data)) { DEBUG_WIN << "Basket[" + folderName() + "]: FAILED to save!"; return false; } Global::bnpView->setUnsavedStatus(false); m_commitdelay.start(10000); // delay is 10 seconds return true; } void BasketScene::commitEdit() { GitWrapper::commitBasket(this); } void BasketScene::aboutToBeActivated() { if (m_finishLoadOnFirstShow) { FOR_EACH_NOTE(note) note->finishLazyLoad(); // relayoutNotes(/*animate=*/false); setFocusedNote(0); // So that during the focusInEvent that will come shortly, the FIRST note is focused. if (Settings::playAnimations() && !decoration()->filterBar()->filterData().isFiltering && Global::bnpView->currentBasket() == this) // No animation when filtering all! animateLoad(); // QTimer::singleShot( 0, this, SLOT(animateLoad()) ); m_finishLoadOnFirstShow = false; } } void BasketScene::reload() { closeEditor(); unbufferizeAll(); // Keep the memory footprint low m_firstNote = 0; m_loaded = false; m_loadingLaunched = false; invalidate(); } void BasketScene::load() { // Load only once: if (m_loadingLaunched) return; m_loadingLaunched = true; DEBUG_WIN << "Basket[" + folderName() + "]: Loading..."; QDomDocument *doc = 0; QString content; // Load properties if (loadFromFile(fullPath() + ".basket", &content)) { doc = new QDomDocument("basket"); if (!doc->setContent(content)) { DEBUG_WIN << "Basket[" + folderName() + "]: FAILED to parse XML!"; delete doc; doc = 0; } } if (isEncrypted()) DEBUG_WIN << "Basket is encrypted."; if (!doc) { DEBUG_WIN << "Basket[" + folderName() + "]: FAILED to load!"; m_loadingLaunched = false; if (isEncrypted()) m_locked = true; Global::bnpView->notesStateChanged(); // Show "Locked" instead of "Loading..." in the statusbar return; } m_locked = false; QDomElement docElem = doc->documentElement(); QDomElement properties = XMLWork::getElement(docElem, "properties"); loadProperties(properties); // Since we are loading, this time the background image will also be loaded! // Now that the background image is loaded and subscribed, we display it during the load process: delete doc; // BEGIN Compatibility with 0.6.0 Pre-Alpha versions: QDomElement notes = XMLWork::getElement(docElem, "notes"); if (notes.isNull()) notes = XMLWork::getElement(docElem, "items"); m_watcher->stopScan(); m_shouldConvertPlainTextNotes = false; // Convert Pre-0.6.0 baskets: plain text notes should be converted to rich text ones once all is loaded! // Load notes m_finishLoadOnFirstShow = (Global::bnpView->currentBasket() != this); loadNotes(notes, 0L); if (m_shouldConvertPlainTextNotes) convertTexts(); m_watcher->startScan(); signalCountsChanged(); if (isColumnsLayout()) { // Count the number of columns: int columnsCount = 0; Note *column = firstNote(); while (column) { ++columnsCount; column = column->next(); } m_columnsCount = columnsCount; } relayoutNotes(false); // On application start, the current basket is not focused yet, so the focus rectangle is not shown when calling focusANote(): if (Global::bnpView->currentBasket() == this) setFocus(); focusANote(); if (Settings::playAnimations() && !decoration()->filterBar()->filterData().isFiltering && Global::bnpView->currentBasket() == this) // No animation when filtering all! animateLoad(); // QTimer::singleShot( 0, this, SLOT(animateLoad()) ); else m_loaded = true; enableActions(); } void BasketScene::filterAgain(bool andEnsureVisible /* = true*/) { newFilter(decoration()->filterData(), andEnsureVisible); } void BasketScene::filterAgainDelayed() { QTimer::singleShot(0, this, SLOT(filterAgain())); } void BasketScene::newFilter(const FilterData &data, bool andEnsureVisible /* = true*/) { if (!isLoaded()) return; // StopWatch::start(20); m_countFounds = 0; for (Note *note = firstNote(); note; note = note->next()) m_countFounds += note->newFilter(data); relayoutNotes(true); signalCountsChanged(); if (hasFocus()) // if (!hasFocus()), focusANote() will be called at focusInEvent() focusANote(); // so, we avoid de-focus a note if it will be re-shown soon if (andEnsureVisible && m_focusedNote != 0L) ensureNoteVisible(m_focusedNote); Global::bnpView->setFiltering(data.isFiltering); // StopWatch::check(20); } bool BasketScene::isFiltering() { return decoration()->filterBar()->filterData().isFiltering; } QString BasketScene::fullPath() { return Global::basketsFolder() + folderName(); } QString BasketScene::fullPathForFileName(const QString &fileName) { return fullPath() + fileName; } /*static*/ QString BasketScene::fullPathForFolderName(const QString &folderName) { return Global::basketsFolder() + folderName; } void BasketScene::setShortcut(QKeySequence shortcut, int action) { QList shortcuts {shortcut}; if (action > 0) { KGlobalAccel::self()->setShortcut(m_action, shortcuts, KGlobalAccel::Autoloading); KGlobalAccel::self()->setDefaultShortcut(m_action, shortcuts, KGlobalAccel::Autoloading); } m_shortcutAction = action; } void BasketScene::activatedShortcut() { Global::bnpView->setCurrentBasket(this); if (m_shortcutAction == 1) Global::bnpView->setActive(true); } void BasketScene::signalCountsChanged() { if (!m_timerCountsChanged.isActive()) { m_timerCountsChanged.setSingleShot(true); m_timerCountsChanged.start(0); } } void BasketScene::countsChangedTimeOut() { emit countsChanged(this); } BasketScene::BasketScene(QWidget *parent, const QString &folderName) //: Q3ScrollView(parent) : QGraphicsScene(parent) , m_noActionOnMouseRelease(false) , m_ignoreCloseEditorOnNextMouseRelease(false) , m_pressPos(-100, -100) , m_canDrag(false) , m_firstNote(0) , m_columnsCount(1) , m_mindMap(false) , m_resizingNote(0L) , m_pickedResizer(0) , m_movingNote(0L) , m_pickedHandle(0, 0) , m_notesToBeDeleted() , m_clickedToInsert(0) , m_zoneToInsert(0) , m_posToInsert(-1, -1) , m_isInsertPopupMenu(false) , m_insertMenuTitle(0) , m_animationTimeLine(0) , m_loaded(false) , m_loadingLaunched(false) , m_locked(false) , m_decryptBox(0) , m_button(0) , m_encryptionType(NoEncryption) #ifdef HAVE_LIBGPGME , m_gpg(0) #endif , m_backgroundPixmap(0) , m_opaqueBackgroundPixmap(0) , m_selectedBackgroundPixmap(0) , m_action(0) , m_shortcutAction(0) , m_hoveredNote(0) , m_hoveredZone(Note::None) , m_lockedHovering(false) , m_underMouse(false) , m_inserterRect() , m_inserterShown(false) , m_inserterSplit(true) , m_inserterTop(false) , m_inserterGroup(false) , m_lastDisableClick(QTime::currentTime()) , m_isSelecting(false) , m_selectionStarted(false) , m_count(0) , m_countFounds(0) , m_countSelecteds(0) , m_folderName(folderName) , m_editor(0) , m_leftEditorBorder(0) , m_rightEditorBorder(0) , m_redirectEditActions(false) , m_editorTrackMouseEvent(false) , m_editorWidth(-1) , m_editorHeight(-1) , m_doNotCloseEditor(false) , m_isDuringDrag(false) , m_draggedNotes() , m_focusedNote(0) , m_startOfShiftSelectionNote(0) , m_finishLoadOnFirstShow(false) , m_relayoutOnNextShow(false) { m_view = new BasketView(this); m_view->setFocusPolicy(Qt::StrongFocus); m_view->setAlignment(Qt::AlignLeft | Qt::AlignTop); m_action = new QAction(this); connect(m_action, SIGNAL(triggered()), this, SLOT(activatedShortcut())); m_action->setObjectName(folderName); KGlobalAccel::self()->setGlobalShortcut(m_action, (QKeySequence())); // We do this in the basket properties dialog (and keep it in sync with the // global one) KActionCollection *ac = Global::bnpView->actionCollection(); ac->setShortcutsConfigurable(m_action, false); if (!m_folderName.endsWith('/')) m_folderName += '/'; // setDragAutoScroll(true); // By default, there is no corner widget: we set one for the corner area to be painted! // If we don't set one and there are two scrollbars present, slowly resizing up the window show graphical glitches in that area! m_cornerWidget = new QWidget(m_view); m_view->setCornerWidget(m_cornerWidget); m_view->viewport()->setAcceptDrops(true); m_view->viewport()->setMouseTracking(true); m_view->viewport()->setAutoFillBackground(false); // Do not clear the widget before paintEvent() because we always draw every pixels (faster and flicker-free) // File Watcher: m_watcher = new KDirWatch(this); connect(m_watcher, SIGNAL(dirty(const QString &)), this, SLOT(watchedFileModified(const QString &))); // connect(m_watcher, SIGNAL(deleted(const QString&)), this, SLOT(watchedFileDeleted(const QString&))); connect(&m_watcherTimer, SIGNAL(timeout()), this, SLOT(updateModifiedNotes())); // Various Connections: connect(&m_autoScrollSelectionTimer, SIGNAL(timeout()), this, SLOT(doAutoScrollSelection())); connect(&m_timerCountsChanged, SIGNAL(timeout()), this, SLOT(countsChangedTimeOut())); connect(&m_inactivityAutoSaveTimer, SIGNAL(timeout()), this, SLOT(inactivityAutoSaveTimeout())); connect(&m_inactivityAutoLockTimer, SIGNAL(timeout()), this, SLOT(inactivityAutoLockTimeout())); #ifdef HAVE_LIBGPGME m_gpg = new KGpgMe(); #endif m_locked = isFileEncrypted(); // setup the delayed commit timer m_commitdelay.setSingleShot(true); connect(&m_commitdelay, SIGNAL(timeout()), this, SLOT(commitEdit())); } void BasketScene::enterEvent(QEvent *) { m_underMouse = true; doHoverEffects(); } void BasketScene::leaveEvent(QEvent *) { m_underMouse = false; doHoverEffects(); if (m_lockedHovering) return; removeInserter(); if (m_hoveredNote) { m_hoveredNote->setHovered(false); m_hoveredNote->setHoveredZone(Note::None); m_hoveredNote->update(); } m_hoveredNote = 0; } void BasketScene::setFocusIfNotInPopupMenu() { if (!qApp->activePopupWidget()) { if (isDuringEdit()) m_editor->graphicsWidget()->setFocus(); else setFocus(); } } void BasketScene::mousePressEvent(QGraphicsSceneMouseEvent *event) { // If user click the basket, focus it! // The focus is delayed because if the click results in showing a popup menu, // the interface flicker by showing the focused rectangle (as the basket gets focus) // and immediately removing it (because the popup menu now have focus). if (!isDuringEdit()) QTimer::singleShot(0, this, SLOT(setFocusIfNotInPopupMenu())); // Convenient variables: bool controlPressed = event->modifiers() & Qt::ControlModifier; bool shiftPressed = event->modifiers() & Qt::ShiftModifier; // Do nothing if we disabled the click some milliseconds sooner. // For instance when a popup menu has been closed with click, we should not do action: if (event->button() == Qt::LeftButton && (qApp->activePopupWidget() || m_lastDisableClick.msecsTo(QTime::currentTime()) <= 80)) { doHoverEffects(); m_noActionOnMouseRelease = true; // But we allow to select: // The code is the same as at the bottom of this method: if (event->button() == Qt::LeftButton) { m_selectionStarted = true; m_selectionBeginPoint = event->scenePos(); m_selectionInvert = controlPressed || shiftPressed; } return; } // if we are editing and no control key are pressed if (m_editor && !shiftPressed && !controlPressed) { // if the mouse is over the editor QPoint view_shift(m_view->horizontalScrollBar()->value(), m_view->verticalScrollBar()->value()); QGraphicsWidget *widget = dynamic_cast(m_view->itemAt((event->scenePos() - view_shift).toPoint())); if (widget && m_editor->graphicsWidget() == widget) { if (event->button() == Qt::LeftButton) { m_editorTrackMouseEvent = true; m_editor->startSelection(event->scenePos()); return; } else if (event->button() == Qt::MiddleButton) { m_editor->paste(event->scenePos(), QClipboard::Selection); return; } } } // Figure out what is the clicked note and zone: Note *clicked = noteAt(event->scenePos()); if (m_editor && (!clicked || (clicked && !(editedNote() == clicked)))) { closeEditor(); } Note::Zone zone = (clicked ? clicked->zoneAt(event->scenePos() - QPointF(clicked->x(), clicked->y())) : Note::None); // Popup Tags menu: if (zone == Note::TagsArrow && !controlPressed && !shiftPressed && event->button() != Qt::MidButton) { if (!clicked->allSelected()) unselectAllBut(clicked); setFocusedNote(clicked); /// /// /// m_startOfShiftSelectionNote = clicked; m_noActionOnMouseRelease = true; popupTagsMenu(clicked); return; } if (event->button() == Qt::LeftButton) { // Prepare to allow drag and drop when moving mouse further: if ((zone == Note::Handle || zone == Note::Group) || (clicked && clicked->allSelected() && (zone == Note::TagsArrow || zone == Note::Custom0 || zone == Note::Content || zone == Note::Link /**/ || zone >= Note::Emblem0 /**/))) { if (!shiftPressed && !controlPressed) { m_pressPos = event->scenePos(); // TODO: Allow to drag emblems to assign them to other notes. Then don't allow drag at Emblem0!! m_canDrag = true; // Saving where we were editing, because during a drag, the mouse can fly over the text edit and move the cursor position: if (m_editor && m_editor->textEdit()) { KTextEdit *editor = m_editor->textEdit(); m_textCursor = editor->textCursor(); } } } // Initializing Resizer move: if (zone == Note::Resizer) { m_resizingNote = clicked; m_pickedResizer = event->scenePos().x() - clicked->rightLimit(); m_noActionOnMouseRelease = true; m_lockedHovering = true; return; } // Select note(s): if (zone == Note::Handle || zone == Note::Group || (zone == Note::GroupExpander && (controlPressed || shiftPressed))) { // closeEditor(); Note *end = clicked; if (clicked->isGroup() && shiftPressed) { if (clicked->containsNote(m_startOfShiftSelectionNote)) { m_startOfShiftSelectionNote = clicked->firstRealChild(); end = clicked->lastRealChild(); } else if (clicked->firstRealChild()->isAfter(m_startOfShiftSelectionNote)) { end = clicked->lastRealChild(); } else { end = clicked->firstRealChild(); } } if (controlPressed && shiftPressed) selectRange(m_startOfShiftSelectionNote, end, /*unselectOthers=*/false); else if (shiftPressed) selectRange(m_startOfShiftSelectionNote, end); else if (controlPressed) clicked->setSelectedRecursively(!clicked->allSelected()); else if (!clicked->allSelected()) unselectAllBut(clicked); setFocusedNote(end); /// /// /// m_startOfShiftSelectionNote = (end->isGroup() ? end->firstRealChild() : end); // m_noActionOnMouseRelease = false; m_noActionOnMouseRelease = true; return; } // Folding/Unfolding group: if (zone == Note::GroupExpander) { clicked->toggleFolded(); if (/*m_animationTimeLine == 0 && */ Settings::playAnimations()) { qWarning() << "Folding animation to be done"; } relayoutNotes(true); m_noActionOnMouseRelease = true; return; } } // Popup menu for tag emblems: if (event->button() == Qt::RightButton && zone >= Note::Emblem0) { if (!clicked->allSelected()) unselectAllBut(clicked); setFocusedNote(clicked); /// /// /// m_startOfShiftSelectionNote = clicked; popupEmblemMenu(clicked, zone - Note::Emblem0); m_noActionOnMouseRelease = true; return; } // Insertion Popup Menu: if ((event->button() == Qt::RightButton) && ((!clicked && isFreeLayout()) || (clicked && (zone == Note::TopInsert || zone == Note::TopGroup || zone == Note::BottomInsert || zone == Note::BottomGroup || zone == Note::BottomColumn)))) { unselectAll(); m_clickedToInsert = clicked; m_zoneToInsert = zone; m_posToInsert = event->scenePos(); QMenu menu(m_view); menu.addActions(Global::bnpView->popupMenu("insert_popup")->actions()); // If we already added a title, remove it because it would be kept and // then added several times. if (m_insertMenuTitle && menu.actions().contains(m_insertMenuTitle)) menu.removeAction(m_insertMenuTitle); QAction *first = menu.actions().value(0); // i18n: Verbs (for the "insert" menu) if (zone == Note::TopGroup || zone == Note::BottomGroup) m_insertMenuTitle = menu.insertSection(first, i18n("Group")); else m_insertMenuTitle = menu.insertSection(first, i18n("Insert")); setInsertPopupMenu(); connect(&menu, SIGNAL(aboutToHide()), this, SLOT(delayedCancelInsertPopupMenu())); connect(&menu, SIGNAL(aboutToHide()), this, SLOT(unlockHovering())); connect(&menu, SIGNAL(aboutToHide()), this, SLOT(disableNextClick())); connect(&menu, SIGNAL(aboutToHide()), this, SLOT(hideInsertPopupMenu())); doHoverEffects(clicked, zone); // In the case where another popup menu was open, we should do that manually! m_lockedHovering = true; menu.exec(QCursor::pos()); m_noActionOnMouseRelease = true; return; } // Note Context Menu: if (event->button() == Qt::RightButton && clicked && !clicked->isColumn() && zone != Note::Resizer) { if (!clicked->allSelected()) unselectAllBut(clicked); setFocusedNote(clicked); /// /// /// if (editedNote() == clicked) { closeEditor(false); clicked->setSelected(true); } m_startOfShiftSelectionNote = (clicked->isGroup() ? clicked->firstRealChild() : clicked); QMenu *menu = Global::bnpView->popupMenu("note_popup"); connect(menu, SIGNAL(aboutToHide()), this, SLOT(unlockHovering())); connect(menu, SIGNAL(aboutToHide()), this, SLOT(disableNextClick())); doHoverEffects(clicked, zone); // In the case where another popup menu was open, we should do that manually! m_lockedHovering = true; menu->exec(QCursor::pos()); m_noActionOnMouseRelease = true; return; } // Paste selection under cursor (but not "create new primary note under cursor" because this is on moveRelease): if (event->button() == Qt::MidButton && zone != Note::Resizer && (!isDuringEdit() || clicked != editedNote())) { if ((Settings::middleAction() != 0) && (event->modifiers() == Qt::ShiftModifier)) { m_clickedToInsert = clicked; m_zoneToInsert = zone; m_posToInsert = event->scenePos(); // closeEditor(); removeInserter(); // If clicked at an insertion line and the new note shows a dialog for editing, NoteType::Id type = (NoteType::Id)0; // hide that inserter before the note edition instead of after the dialog is closed switch (Settings::middleAction()) { case 1: m_isInsertPopupMenu = true; pasteNote(); break; case 2: type = NoteType::Image; break; case 3: type = NoteType::Link; break; case 4: type = NoteType::Launcher; break; default: m_noActionOnMouseRelease = false; return; } if (type != 0) { m_ignoreCloseEditorOnNextMouseRelease = true; Global::bnpView->insertEmpty(type); } } else { if (clicked) zone = clicked->zoneAt(event->scenePos() - QPoint(clicked->x(), clicked->y()), true); // closeEditor(); clickedToInsert(event, clicked, zone); save(); } m_noActionOnMouseRelease = true; return; } // Finally, no action has been done during pressEvent, so an action can be done on releaseEvent: m_noActionOnMouseRelease = false; /* Selection scenario: * On contentsMousePressEvent, put m_selectionStarted to true and init Begin and End selection point. * On contentsMouseMoveEvent, if m_selectionStarted, update End selection point, update selection rect, * and if it's larger, switching to m_isSelecting mode: we can draw the selection rectangle. */ // Prepare selection: if (event->button() == Qt::LeftButton) { m_selectionStarted = true; m_selectionBeginPoint = event->scenePos(); // We usually invert the selection with the Ctrl key, but some environments (like GNOME or The Gimp) do it with the Shift key. // Since the Shift key has no specific usage, we allow to invert selection ALSO with Shift for Gimp people m_selectionInvert = controlPressed || shiftPressed; } } void BasketScene::delayedCancelInsertPopupMenu() { QTimer::singleShot(0, this, SLOT(cancelInsertPopupMenu())); } void BasketScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { if (event->reason() == QGraphicsSceneContextMenuEvent::Keyboard) { if (countFounds /*countShown*/ () == 0) { // TODO: Count shown!! QMenu *menu = Global::bnpView->popupMenu("insert_popup"); setInsertPopupMenu(); connect(menu, SIGNAL(aboutToHide()), this, SLOT(delayedCancelInsertPopupMenu())); connect(menu, SIGNAL(aboutToHide()), this, SLOT(unlockHovering())); connect(menu, SIGNAL(aboutToHide()), this, SLOT(disableNextClick())); removeInserter(); m_lockedHovering = true; menu->exec(m_view->mapToGlobal(QPoint(0, 0))); } else { if (!m_focusedNote->isSelected()) unselectAllBut(m_focusedNote); setFocusedNote(m_focusedNote); /// /// /// m_startOfShiftSelectionNote = (m_focusedNote->isGroup() ? m_focusedNote->firstRealChild() : m_focusedNote); // Popup at bottom (or top) of the focused note, if visible : QMenu *menu = Global::bnpView->popupMenu("note_popup"); connect(menu, SIGNAL(aboutToHide()), this, SLOT(unlockHovering())); connect(menu, SIGNAL(aboutToHide()), this, SLOT(disableNextClick())); doHoverEffects(m_focusedNote, Note::Content); // In the case where another popup menu was open, we should do that manually! m_lockedHovering = true; menu->exec(noteVisibleRect(m_focusedNote).bottomLeft().toPoint()); } } } QRectF BasketScene::noteVisibleRect(Note *note) { QRectF rect(QPointF(note->x(), note->y()), QSizeF(note->width(), note->height())); QPoint basketPoint = m_view->mapToGlobal(QPoint(0, 0)); rect.moveTopLeft(rect.topLeft() + basketPoint + QPoint(m_view->frameWidth(), m_view->frameWidth())); // Now, rect contain the global note rectangle on the screen. // We have to clip it by the basket widget : // if (rect.bottom() > basketPoint.y() + visibleHeight() + 1) { // Bottom too... bottom // rect.setBottom(basketPoint.y() + visibleHeight() + 1); if (rect.bottom() > basketPoint.y() + m_view->viewport()->height() + 1) { // Bottom too... bottom rect.setBottom(basketPoint.y() + m_view->viewport()->height() + 1); if (rect.height() <= 0) // Have at least one visible pixel of height rect.setTop(rect.bottom()); } if (rect.top() < basketPoint.y() + m_view->frameWidth()) { // Top too... top rect.setTop(basketPoint.y() + m_view->frameWidth()); if (rect.height() <= 0) rect.setBottom(rect.top()); } // if (rect.right() > basketPoint.x() + visibleWidth() + 1) { // Right too... right // rect.setRight(basketPoint.x() + visibleWidth() + 1); if (rect.right() > basketPoint.x() + m_view->viewport()->width() + 1) { // Right too... right rect.setRight(basketPoint.x() + m_view->viewport()->width() + 1); if (rect.width() <= 0) // Have at least one visible pixel of width rect.setLeft(rect.right()); } if (rect.left() < basketPoint.x() + m_view->frameWidth()) { // Left too... left rect.setLeft(basketPoint.x() + m_view->frameWidth()); if (rect.width() <= 0) rect.setRight(rect.left()); } return rect; } void BasketScene::disableNextClick() { m_lastDisableClick = QTime::currentTime(); } void BasketScene::recomputeAllStyles() { FOR_EACH_NOTE(note) note->recomputeAllStyles(); } void BasketScene::removedStates(const QList &deletedStates) { bool modifiedBasket = false; FOR_EACH_NOTE(note) if (note->removedStates(deletedStates)) modifiedBasket = true; if (modifiedBasket) save(); } void BasketScene::insertNote(Note *note, Note *clicked, int zone, const QPointF &pos, bool animateNewPosition) { if (!note) { qDebug() << "Wanted to insert NO note"; return; } if (clicked && zone == Note::BottomColumn) { // When inserting at the bottom of a column, it's obvious the new note SHOULD inherit tags. // We ensure that by changing the insertion point after the last note of the column: Note *last = clicked->lastChild(); if (last) { clicked = last; zone = Note::BottomInsert; } } /// Insertion at the bottom of a column: if (clicked && zone == Note::BottomColumn) { note->setWidth(clicked->rightLimit() - clicked->x()); Note *lastChild = clicked->lastChild(); if (!animateNewPosition || !Settings::playAnimations()) for (Note *n = note; n; n = n->next()) { n->setXRecursively(clicked->x()); n->setYRecursively((lastChild ? lastChild : clicked)->bottom() + 1); } appendNoteIn(note, clicked); /// Insertion relative to a note (top/bottom, insert/group): } else if (clicked) { note->setWidth(clicked->width()); if (!animateNewPosition || !Settings::playAnimations()) for (Note *n = note; n; n = n->next()) { if (zone == Note::TopGroup || zone == Note::BottomGroup) n->setXRecursively(clicked->x() + Note::GROUP_WIDTH); else n->setXRecursively(clicked->x()); if (zone == Note::TopInsert || zone == Note::TopGroup) n->setYRecursively(clicked->y()); else n->setYRecursively(clicked->bottom() + 1); } if (zone == Note::TopInsert) { appendNoteBefore(note, clicked); } else if (zone == Note::BottomInsert) { appendNoteAfter(note, clicked); } else if (zone == Note::TopGroup) { groupNoteBefore(note, clicked); } else if (zone == Note::BottomGroup) { groupNoteAfter(note, clicked); } /// Free insertion: } else if (isFreeLayout()) { // Group if note have siblings: if (note->next()) { Note *group = new Note(this); for (Note *n = note; n; n = n->next()) n->setParentNote(group); group->setFirstChild(note); note = group; } // Insert at cursor position: const int initialWidth = 250; note->setWidth(note->isGroup() ? Note::GROUP_WIDTH : initialWidth); if (note->isGroup() && note->firstChild()) note->setInitialHeight(note->firstChild()->height()); // note->setGroupWidth(initialWidth); /*if (animateNewPosition && Settings::playAnimations()) note->setFinalPosition(pos.x(), pos.y()); else {*/ note->setXRecursively(pos.x()); note->setYRecursively(pos.y()); //} appendNoteAfter(note, lastNote()); } relayoutNotes(true); } void BasketScene::clickedToInsert(QGraphicsSceneMouseEvent *event, Note *clicked, /*Note::Zone*/ int zone) { Note *note; if (event->button() == Qt::MidButton) note = NoteFactory::dropNote(QApplication::clipboard()->mimeData(QClipboard::Selection), this); else note = NoteFactory::createNoteText("", this); if (!note) return; insertNote(note, clicked, zone, QPointF(event->scenePos()), /*animateNewPosition=*/false); // ensureNoteVisible(lastInsertedNote()); // TODO: in insertNote() if (event->button() != Qt::MidButton) { removeInserter(); // Case: user clicked below a column to insert, the note is inserted and doHoverEffects() put a new inserter below. We don't want it. closeEditor(); noteEdit(note, /*justAdded=*/true); } } void BasketScene::dragEnterEvent(QGraphicsSceneDragDropEvent *event) { m_isDuringDrag = true; Global::bnpView->updateStatusBarHint(); if (NoteDrag::basketOf(event->mimeData()) == this) { m_draggedNotes = NoteDrag::notesOf(event); NoteDrag::saveNoteSelectionToList(selectedNotes()); } event->accept(); } void BasketScene::dragMoveEvent(QGraphicsSceneDragDropEvent *event) { // m_isDuringDrag = true; // if (isLocked()) // return; // FIXME: viewportToContents does NOT work !!! // QPoint pos = viewportToContents(event->pos()); // QPoint pos( event->pos().x() + contentsX(), event->pos().y() + contentsY() ); // if (insertAtCursorPos()) // computeInsertPlace(pos); doHoverEffects(event->scenePos()); // showFrameInsertTo(); if (isFreeLayout() || noteAt(event->scenePos())) // Cursor before rightLimit() or hovering the dragged source notes acceptDropEvent(event); else { event->setAccepted(false); } /* Note *hoveredNote = noteAt(event->pos().x(), event->pos().y()); if ( (isColumnsLayout() && !hoveredNote) || (draggedNotes().contains(hoveredNote)) ) { event->acceptAction(false); event->accept(false); } else acceptDropEvent(event);*/ // A workaround since QScrollView::dragAutoScroll seem to have no effect : // ensureVisible(event->pos().x() + contentsX(), event->pos().y() + contentsY(), 30, 30); // QScrollView::dragMoveEvent(event); } void BasketScene::dragLeaveEvent(QGraphicsSceneDragDropEvent *) { // resetInsertTo(); m_isDuringDrag = false; m_draggedNotes.clear(); NoteDrag::selectedNotes.clear(); m_noActionOnMouseRelease = true; emit resetStatusBarText(); doHoverEffects(); } void BasketScene::dropEvent(QGraphicsSceneDragDropEvent *event) { QPointF pos = event->scenePos(); qDebug() << "Drop Event at position " << pos.x() << ":" << pos.y(); m_isDuringDrag = false; emit resetStatusBarText(); // if (isLocked()) // return; // Do NOT check the bottom&right borders. // Because imagine someone drag&drop a big note from the top to the bottom of a big basket (with big vertical scrollbars), // the note is first removed, and relayoutNotes() compute the new height that is smaller // Then noteAt() is called for the mouse pointer position, because the basket is now smaller, the cursor is out of boundaries!!! // Should, of course, not return 0: Note *clicked = noteAt(pos); if (NoteFactory::movingNotesInTheSameBasket(event->mimeData(), this, event->dropAction()) && event->dropAction() == Qt::MoveAction) { m_doNotCloseEditor = true; } Note *note = NoteFactory::dropNote(event->mimeData(), this, true, event->dropAction(), dynamic_cast(event->source())); if (note) { Note::Zone zone = (clicked ? clicked->zoneAt(pos - QPointF(clicked->x(), clicked->y()), /*toAdd=*/true) : Note::None); bool animateNewPosition = NoteFactory::movingNotesInTheSameBasket(event->mimeData(), this, event->dropAction()); if (animateNewPosition) { FOR_EACH_NOTE(n) n->setOnTop(false); // FOR_EACH_NOTE_IN_CHUNK(note) for (Note *n = note; n; n = n->next()) n->setOnTop(true); } insertNote(note, clicked, zone, pos, animateNewPosition); // If moved a note on bottom, contentsHeight has been diminished, then view scrolled up, and we should re-scroll the view down: ensureNoteVisible(note); // if (event->button() != Qt::MidButton) { // removeInserter(); // Case: user clicked below a column to insert, the note is inserted and doHoverEffects() put a new inserter below. We don't want it. // } // resetInsertTo(); // doHoverEffects(); called by insertNote() save(); } m_draggedNotes.clear(); NoteDrag::selectedNotes.clear(); m_doNotCloseEditor = false; // When starting the drag, we saved where we were editing. // This is because during a drag, the mouse can fly over the text edit and move the cursor position, and even HIDE the cursor. // So we re-show the cursor, and re-position it at the right place: if (m_editor && m_editor->textEdit()) { KTextEdit *editor = m_editor->textEdit(); editor->setTextCursor(m_textCursor); } } // handles dropping of a note to basket that is not shown // (usually through its entry in the basket list) void BasketScene::blindDrop(QGraphicsSceneDragDropEvent *event) { if (!m_isInsertPopupMenu && redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->paste(); else if (m_editor->lineEdit()) m_editor->lineEdit()->paste(); } else { if (!isLoaded()) { Global::bnpView->showPassiveLoading(this); load(); } closeEditor(); unselectAll(); Note *note = NoteFactory::dropNote(event->mimeData(), this, true, event->dropAction(), dynamic_cast(event->source())); if (note) { insertCreatedNote(note); // unselectAllBut(note); if (Settings::usePassivePopup()) Global::bnpView->showPassiveDropped(i18n("Dropped to basket %1", m_basketName)); } } save(); } void BasketScene::blindDrop(const QMimeData *mimeData, Qt::DropAction dropAction, QObject *source) { if (!m_isInsertPopupMenu && redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->paste(); else if (m_editor->lineEdit()) m_editor->lineEdit()->paste(); } else { if (!isLoaded()) { Global::bnpView->showPassiveLoading(this); load(); } closeEditor(); unselectAll(); Note *note = NoteFactory::dropNote(mimeData, this, true, dropAction, dynamic_cast(source)); if (note) { insertCreatedNote(note); // unselectAllBut(note); if (Settings::usePassivePopup()) Global::bnpView->showPassiveDropped(i18n("Dropped to basket %1", m_basketName)); } } save(); } void BasketScene::insertEmptyNote(int type) { if (!isLoaded()) load(); if (isDuringEdit()) closeEditor(); Note *note = NoteFactory::createEmptyNote((NoteType::Id)type, this); insertCreatedNote(note /*, / *edit=* /true*/); noteEdit(note, /*justAdded=*/true); } void BasketScene::insertWizard(int type) { saveInsertionData(); Note *note = 0; switch (type) { default: case 1: note = NoteFactory::importKMenuLauncher(this); break; case 2: note = NoteFactory::importIcon(this); break; case 3: note = NoteFactory::importFileContent(this); break; } if (!note) return; restoreInsertionData(); insertCreatedNote(note); unselectAllBut(note); resetInsertionData(); } void BasketScene::insertColor(const QColor &color) { Note *note = NoteFactory::createNoteColor(color, this); restoreInsertionData(); insertCreatedNote(note); unselectAllBut(note); resetInsertionData(); } void BasketScene::insertImage(const QPixmap &image) { Note *note = NoteFactory::createNoteImage(image, this); restoreInsertionData(); insertCreatedNote(note); unselectAllBut(note); resetInsertionData(); } void BasketScene::pasteNote(QClipboard::Mode mode) { if (!m_isInsertPopupMenu && redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->paste(); else if (m_editor->lineEdit()) m_editor->lineEdit()->paste(); } else { if (!isLoaded()) { Global::bnpView->showPassiveLoading(this); load(); } closeEditor(); unselectAll(); Note *note = NoteFactory::dropNote(QApplication::clipboard()->mimeData(mode), this); if (note) { insertCreatedNote(note); // unselectAllBut(note); } } } void BasketScene::insertCreatedNote(Note *note) { // Get the insertion data if the user clicked inside the basket: Note *clicked = m_clickedToInsert; int zone = m_zoneToInsert; QPointF pos = m_posToInsert; // If it isn't the case, use the default position: if (!clicked && (pos.x() < 0 || pos.y() < 0)) { // Insert right after the focused note: focusANote(); if (m_focusedNote) { clicked = m_focusedNote; zone = (m_focusedNote->isFree() ? Note::BottomGroup : Note::BottomInsert); pos = QPointF(m_focusedNote->x(), m_focusedNote->bottom()); // Insert at the end of the last column: } else if (isColumnsLayout()) { Note *column = /*(Settings::newNotesPlace == 0 ?*/ firstNote() /*: lastNote())*/; /*if (Settings::newNotesPlace == 0 && column->firstChild()) { // On Top, if at least one child in the column clicked = column->firstChild(); zone = Note::TopInsert; } else { // On Bottom*/ clicked = column; zone = Note::BottomColumn; /*}*/ // Insert at free position: } else { pos = QPointF(0, 0); } } insertNote(note, clicked, zone, pos); // ensureNoteVisible(lastInsertedNote()); removeInserter(); // Case: user clicked below a column to insert, the note is inserted and doHoverEffects() put a new inserter below. We don't want it. // resetInsertTo(); save(); } void BasketScene::saveInsertionData() { m_savedClickedToInsert = m_clickedToInsert; m_savedZoneToInsert = m_zoneToInsert; m_savedPosToInsert = m_posToInsert; } void BasketScene::restoreInsertionData() { m_clickedToInsert = m_savedClickedToInsert; m_zoneToInsert = m_savedZoneToInsert; m_posToInsert = m_savedPosToInsert; } void BasketScene::resetInsertionData() { m_clickedToInsert = 0; m_zoneToInsert = 0; m_posToInsert = QPoint(-1, -1); } void BasketScene::hideInsertPopupMenu() { QTimer::singleShot(50 /*ms*/, this, SLOT(timeoutHideInsertPopupMenu())); } void BasketScene::timeoutHideInsertPopupMenu() { resetInsertionData(); } void BasketScene::acceptDropEvent(QGraphicsSceneDragDropEvent *event, bool preCond) { // FIXME: Should not accept all actions! Or not all actions (link not supported?!) // event->acceptAction(preCond && 1); // event->accept(preCond); event->setAccepted(preCond); } void BasketScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { // Now disallow drag and mouse redirection m_canDrag = false; if (m_editorTrackMouseEvent) { m_editorTrackMouseEvent = false; m_editor->endSelection(m_pressPos); return; } // Cancel Resizer move: if (m_resizingNote) { m_resizingNote = 0; m_pickedResizer = 0; m_lockedHovering = false; doHoverEffects(); save(); } // Cancel Note move: /* if (m_movingNote) { m_movingNote = 0; m_pickedHandle = QPoint(0, 0); m_lockedHovering = false; //doHoverEffects(); save(); }*/ // Cancel Selection rectangle: if (m_isSelecting) { m_isSelecting = false; stopAutoScrollSelection(); resetWasInLastSelectionRect(); doHoverEffects(); invalidate(m_selectionRect); } m_selectionStarted = false; Note *clicked = noteAt(event->scenePos()); Note::Zone zone = (clicked ? clicked->zoneAt(event->scenePos() - QPointF(clicked->x(), clicked->y())) : Note::None); if ((zone == Note::Handle || zone == Note::Group) && editedNote() && editedNote() == clicked) { if (m_ignoreCloseEditorOnNextMouseRelease) m_ignoreCloseEditorOnNextMouseRelease = false; else { bool editedNoteStillThere = closeEditor(); if (editedNoteStillThere) // clicked->setSelected(true); unselectAllBut(clicked); } } /* if (event->buttons() == 0 && (zone == Note::Group || zone == Note::Handle)) { closeEditor(); unselectAllBut(clicked); } */ // Do nothing if an action has already been made during mousePressEvent, // or if user made a selection and canceled it by regressing to a very small rectangle. if (m_noActionOnMouseRelease) return; // We immediately set it to true, to avoid actions set on mouseRelease if NO mousePress event has been triggered. // This is the case when a popup menu is shown, and user click to the basket area to close it: // the menu then receive the mousePress event and the basket area ONLY receive the mouseRelease event. // Obviously, nothing should be done in this case: m_noActionOnMouseRelease = true; if (event->button() == Qt::MidButton && zone != Note::Resizer && (!isDuringEdit() || clicked != editedNote())) { if ((Settings::middleAction() != 0) && (event->modifiers() == Qt::ShiftModifier)) { m_clickedToInsert = clicked; m_zoneToInsert = zone; m_posToInsert = event->scenePos(); closeEditor(); removeInserter(); // If clicked at an insertion line and the new note shows a dialog for editing, NoteType::Id type = (NoteType::Id)0; // hide that inserter before the note edition instead of after the dialog is closed switch (Settings::middleAction()) { case 5: type = NoteType::Color; break; case 6: Global::bnpView->grabScreenshot(); return; case 7: Global::bnpView->slotColorFromScreen(); return; case 8: Global::bnpView->insertWizard(3); // loadFromFile return; case 9: Global::bnpView->insertWizard(1); // importKMenuLauncher return; case 10: Global::bnpView->insertWizard(2); // importIcon return; } if (type != 0) { m_ignoreCloseEditorOnNextMouseRelease = true; Global::bnpView->insertEmpty(type); return; } } } // Note *clicked = noteAt(event->pos().x(), event->pos().y()); if (!clicked) { if (isFreeLayout() && event->button() == Qt::LeftButton) { clickedToInsert(event); save(); } return; } // Note::Zone zone = clicked->zoneAt( event->pos() - QPoint(clicked->x(), clicked->y()) ); // Convenient variables: bool controlPressed = event->modifiers() & Qt::ControlModifier; bool shiftPressed = event->modifiers() & Qt::ShiftModifier; if (clicked && zone != Note::None && zone != Note::BottomColumn && zone != Note::Resizer && (controlPressed || shiftPressed)) { if (controlPressed && shiftPressed) selectRange(m_startOfShiftSelectionNote, clicked, /*unselectOthers=*/false); else if (shiftPressed) selectRange(m_startOfShiftSelectionNote, clicked); else if (controlPressed) clicked->setSelectedRecursively(!clicked->allSelected()); setFocusedNote(clicked); /// /// /// m_startOfShiftSelectionNote = (clicked->isGroup() ? clicked->firstRealChild() : clicked); m_noActionOnMouseRelease = true; return; } // Switch tag states: if (zone >= Note::Emblem0) { if (event->button() == Qt::LeftButton) { int icons = -1; for (State::List::iterator it = clicked->states().begin(); it != clicked->states().end(); ++it) { if (!(*it)->emblem().isEmpty()) icons++; if (icons == zone - Note::Emblem0) { State *state = (*it)->nextState(); if (!state) return; it = clicked->states().insert(it, state); ++it; clicked->states().erase(it); clicked->recomputeStyle(); clicked->unbufferize(); clicked->update(); updateEditorAppearance(); filterAgain(); save(); break; } } return; } /* else if (event->button() == Qt::RightButton) { popupEmblemMenu(clicked, zone - Note::Emblem0); return; }*/ } // Insert note or past clipboard: QString text; // Note *note; QString link; // int zone = zone; if (event->button() == Qt::MidButton && zone == Note::Resizer) return; // zone = clicked->zoneAt( event->pos() - QPoint(clicked->x(), clicked->y()), true ); if (event->button() == Qt::RightButton && (clicked->isColumn() || zone == Note::Resizer)) return; if (clicked->isGroup() && zone == Note::None) return; switch (zone) { case Note::Handle: case Note::Group: // We select note on mousePress if it was unselected or Ctrl is pressed. // But the user can want to drag select_s_ notes, so it the note is selected, we only select it alone on mouseRelease: if (event->buttons() == 0) { qDebug() << "EXEC"; if (!(event->modifiers() & Qt::ControlModifier) && clicked->allSelected()) unselectAllBut(clicked); if (zone == Note::Handle && isDuringEdit() && editedNote() == clicked) { closeEditor(); clicked->setSelected(true); } } break; case Note::Custom0: // unselectAllBut(clicked); setFocusedNote(clicked); noteOpen(clicked); break; case Note::GroupExpander: case Note::TagsArrow: break; case Note::Link: link = clicked->linkAt(event->scenePos() - QPoint(clicked->x(), clicked->y())); if (!link.isEmpty()) { if (link == "basket-internal-remove-basket") { // TODO: ask confirmation: "Do you really want to delete the welcome baskets?\n You can re-add them at any time in the Help menu." Global::bnpView->doBasketDeletion(this); } else if (link == "basket-internal-import") { QMenu *menu = Global::bnpView->popupMenu("fileimport"); menu->exec(event->screenPos()); } else if (link.startsWith("basket://")) { emit crossReference(link); } else { KRun *run = new KRun(QUrl::fromUserInput(link), m_view->window()); // open the URL. run->setAutoDelete(true); } break; } // If there is no link, edit note content case Note::Content: { if (m_editor && m_editor->note() == clicked && m_editor->graphicsWidget()) { m_editor->setCursorTo(event->scenePos()); } else { closeEditor(); unselectAllBut(clicked); noteEdit(clicked, /*justAdded=*/false, event->scenePos()); QGraphicsScene::mouseReleaseEvent(event); } break; } case Note::TopInsert: case Note::TopGroup: case Note::BottomInsert: case Note::BottomGroup: case Note::BottomColumn: clickedToInsert(event, clicked, zone); save(); break; case Note::None: default: KMessageBox::information(m_view->viewport(), i18n("This message should never appear. If it does, this program is buggy! " "Please report the bug to the developer.")); break; } } void BasketScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { Note *clicked = noteAt(event->scenePos()); Note::Zone zone = (clicked ? clicked->zoneAt(event->scenePos() - QPointF(clicked->x(), clicked->y())) : Note::None); if (event->button() == Qt::LeftButton && (zone == Note::Group || zone == Note::Handle)) { doCopy(CopyToSelection); m_noActionOnMouseRelease = true; } else mousePressEvent(event); } void BasketScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { // redirect this event to the editor if track mouse event is active if (m_editorTrackMouseEvent && (m_pressPos - event->scenePos()).manhattanLength() > QApplication::startDragDistance()) { m_editor->updateSelection(event->scenePos()); return; } // Drag the notes: if (m_canDrag && (m_pressPos - event->scenePos()).manhattanLength() > QApplication::startDragDistance()) { m_canDrag = false; m_isSelecting = false; // Don't draw selection rectangle ater drag! m_selectionStarted = false; NoteSelection *selection = selectedNotes(); if (selection->firstStacked()) { QDrag *d = NoteDrag::dragObject(selection, /*cutting=*/false, /*source=*/m_view); // d will be deleted by QT /*bool shouldRemove = */ d->exec(); // delete selection; // Never delete because URL is dragged and the file must be available for the extern application // if (shouldRemove && d->target() == 0) // If target is another application that request to remove the note // emit wantDelete(this); } return; } // Moving a Resizer: if (m_resizingNote) { qreal groupWidth = event->scenePos().x() - m_resizingNote->x() - m_pickedResizer; qreal minRight = m_resizingNote->minRight(); // int maxRight = 100 * contentsWidth(); // A big enough value (+infinity) for free layouts. qreal maxRight = 100 * sceneRect().width(); // A big enough value (+infinity) for free layouts. Note *nextColumn = m_resizingNote->next(); if (m_resizingNote->isColumn()) { if (nextColumn) maxRight = nextColumn->x() + nextColumn->rightLimit() - nextColumn->minRight() - Note::RESIZER_WIDTH; else // maxRight = contentsWidth(); maxRight = sceneRect().width(); } if (groupWidth > maxRight - m_resizingNote->x()) groupWidth = maxRight - m_resizingNote->x(); if (groupWidth < minRight - m_resizingNote->x()) groupWidth = minRight - m_resizingNote->x(); qreal delta = groupWidth - m_resizingNote->groupWidth(); m_resizingNote->setGroupWidth(groupWidth); // If resizing columns: if (m_resizingNote->isColumn()) { Note *column = m_resizingNote; if ((column = column->next())) { // Next columns should not have them X coordinate animated, because it would flicker: column->setXRecursively(column->x() + delta); // And the resizer should resize the TWO sibling columns, and not push the other columns on th right: column->setGroupWidth(column->groupWidth() - delta); } } relayoutNotes(true); } // Moving a Note: /* if (m_movingNote) { int x = event->pos().x() - m_pickedHandle.x(); int y = event->pos().y() - m_pickedHandle.y(); if (x < 0) x = 0; if (y < 0) y = 0; m_movingNote->setX(x); m_movingNote->setY(y); m_movingNote->relayoutAt(x, y, / *animate=* /false); relayoutNotes(true); } */ // Dragging the selection rectangle: if (m_selectionStarted) doAutoScrollSelection(); doHoverEffects(event->scenePos()); } void BasketScene::doAutoScrollSelection() { static const int AUTO_SCROLL_MARGIN = 50; // pixels static const int AUTO_SCROLL_DELAY = 100; // milliseconds QPoint pos = m_view->mapFromGlobal(QCursor::pos()); // Do the selection: if (m_isSelecting) invalidate(m_selectionRect); m_selectionEndPoint = m_view->mapToScene(pos); m_selectionRect = QRectF(m_selectionBeginPoint, m_selectionEndPoint).normalized(); if (m_selectionRect.left() < 0) m_selectionRect.setLeft(0); if (m_selectionRect.top() < 0) m_selectionRect.setTop(0); // if (m_selectionRect.right() >= contentsWidth()) m_selectionRect.setRight(contentsWidth() - 1); // if (m_selectionRect.bottom() >= contentsHeight()) m_selectionRect.setBottom(contentsHeight() - 1); if (m_selectionRect.right() >= sceneRect().width()) m_selectionRect.setRight(sceneRect().width() - 1); if (m_selectionRect.bottom() >= sceneRect().height()) m_selectionRect.setBottom(sceneRect().height() - 1); if ((m_selectionBeginPoint - m_selectionEndPoint).manhattanLength() > QApplication::startDragDistance()) { m_isSelecting = true; selectNotesIn(m_selectionRect, m_selectionInvert); invalidate(m_selectionRect); m_noActionOnMouseRelease = true; } else { // If the user was selecting but cancel by making the rectangle too small, cancel it really!!! if (m_isSelecting) { if (m_selectionInvert) selectNotesIn(QRectF(), m_selectionInvert); else unselectAllBut(0); // TODO: unselectAll(); } if (m_isSelecting) resetWasInLastSelectionRect(); m_isSelecting = false; stopAutoScrollSelection(); return; } // Do the auto-scrolling: // FIXME: It's still flickering // QRectF insideRect(AUTO_SCROLL_MARGIN, AUTO_SCROLL_MARGIN, visibleWidth() - 2*AUTO_SCROLL_MARGIN, visibleHeight() - 2*AUTO_SCROLL_MARGIN); QRectF insideRect(AUTO_SCROLL_MARGIN, AUTO_SCROLL_MARGIN, m_view->viewport()->width() - 2 * AUTO_SCROLL_MARGIN, m_view->viewport()->height() - 2 * AUTO_SCROLL_MARGIN); int dx = 0; int dy = 0; if (pos.y() < AUTO_SCROLL_MARGIN) dy = pos.y() - AUTO_SCROLL_MARGIN; else if (pos.y() > m_view->viewport()->height() - AUTO_SCROLL_MARGIN) dy = pos.y() - m_view->viewport()->height() + AUTO_SCROLL_MARGIN; // else if (pos.y() > visibleHeight() - AUTO_SCROLL_MARGIN) // dy = pos.y() - visibleHeight() + AUTO_SCROLL_MARGIN; if (pos.x() < AUTO_SCROLL_MARGIN) dx = pos.x() - AUTO_SCROLL_MARGIN; else if (pos.x() > m_view->viewport()->width() - AUTO_SCROLL_MARGIN) dx = pos.x() - m_view->viewport()->width() + AUTO_SCROLL_MARGIN; // else if (pos.x() > visibleWidth() - AUTO_SCROLL_MARGIN) // dx = pos.x() - visibleWidth() + AUTO_SCROLL_MARGIN; if (dx || dy) { qApp->sendPostedEvents(); // Do the repaints, because the scrolling will make the area to repaint to be wrong // scrollBy(dx, dy); if (!m_autoScrollSelectionTimer.isActive()) m_autoScrollSelectionTimer.start(AUTO_SCROLL_DELAY); } else stopAutoScrollSelection(); } void BasketScene::stopAutoScrollSelection() { m_autoScrollSelectionTimer.stop(); } void BasketScene::resetWasInLastSelectionRect() { Note *note = m_firstNote; while (note) { note->resetWasInLastSelectionRect(); note = note->next(); } } void BasketScene::selectAll() { if (redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->selectAll(); else if (m_editor->lineEdit()) m_editor->lineEdit()->selectAll(); } else { // First select all in the group, then in the parent group... Note *child = m_focusedNote; Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : 0); while (parent) { if (!parent->allSelected()) { parent->setSelectedRecursively(true); return; } child = parent; parent = parent->parentNote(); } // Then, select all: FOR_EACH_NOTE(note) note->setSelectedRecursively(true); } } void BasketScene::unselectAll() { if (redirectEditActions()) { if (m_editor->textEdit()) { QTextCursor cursor = m_editor->textEdit()->textCursor(); cursor.clearSelection(); m_editor->textEdit()->setTextCursor(cursor); selectionChangedInEditor(); // THIS IS NOT EMITTED BY Qt!!! } else if (m_editor->lineEdit()) m_editor->lineEdit()->deselect(); } else { if (countSelecteds() > 0) // Optimization FOR_EACH_NOTE(note) note->setSelectedRecursively(false); } } void BasketScene::invertSelection() { FOR_EACH_NOTE(note) note->invertSelectionRecursively(); } void BasketScene::unselectAllBut(Note *toSelect) { FOR_EACH_NOTE(note) note->unselectAllBut(toSelect); } void BasketScene::invertSelectionOf(Note *toSelect) { FOR_EACH_NOTE(note) note->invertSelectionOf(toSelect); } void BasketScene::selectNotesIn(const QRectF &rect, bool invertSelection, bool unselectOthers /*= true*/) { FOR_EACH_NOTE(note) note->selectIn(rect, invertSelection, unselectOthers); } void BasketScene::doHoverEffects() { doHoverEffects(m_view->mapToScene(m_view->viewport()->mapFromGlobal(QCursor::pos()))); } void BasketScene::doHoverEffects(Note *note, Note::Zone zone, const QPointF &pos) { // Inform the old and new hovered note (if any): Note *oldHoveredNote = m_hoveredNote; if (note != m_hoveredNote) { if (m_hoveredNote) { m_hoveredNote->setHovered(false); m_hoveredNote->setHoveredZone(Note::None); m_hoveredNote->update(); } m_hoveredNote = note; if (m_hoveredNote) { m_hoveredNote->setHovered(true); } } // If we are hovering a note, compute which zone is hovered and inform the note: if (m_hoveredNote) { if (zone != m_hoveredZone || oldHoveredNote != m_hoveredNote) { m_hoveredZone = zone; m_hoveredNote->setHoveredZone(zone); m_view->viewport()->setCursor(m_hoveredNote->cursorFromZone(zone)); m_hoveredNote->update(); } // If we are hovering an insert line zone, update this thing: if (zone == Note::TopInsert || zone == Note::TopGroup || zone == Note::BottomInsert || zone == Note::BottomGroup || zone == Note::BottomColumn) { placeInserter(m_hoveredNote, zone); } else { removeInserter(); } // If we are hovering an embedded link in a rich text element, show the destination in the statusbar: if (zone == Note::Link) emit setStatusBarText(m_hoveredNote->linkAt(pos - QPoint(m_hoveredNote->x(), m_hoveredNote->y()))); else if (m_hoveredNote->content()) emit setStatusBarText(m_hoveredNote->content()->statusBarMessage(m_hoveredZone)); // resetStatusBarText(); // If we aren't hovering a note, reset all: } else { if (isFreeLayout() && !isSelecting()) m_view->viewport()->setCursor(Qt::CrossCursor); else m_view->viewport()->unsetCursor(); m_hoveredZone = Note::None; removeInserter(); emit resetStatusBarText(); } } void BasketScene::doHoverEffects(const QPointF &pos) { // if (isDuringEdit()) // viewport()->unsetCursor(); // Do we have the right to do hover effects? if (!m_loaded || m_lockedHovering) { return; } // enterEvent() (mouse enter in the widget) set m_underMouse to true, and leaveEvent() make it false. // But some times the enterEvent() is not trigerred: eg. when dragging the scrollbar: // Ending the drag INSIDE the basket area will make NO hoverEffects() because m_underMouse is false. // User need to leave the area and re-enter it to get effects. // This hack solve that by dismissing the m_underMouse variable: // Don't do hover effects when a popup menu is opened. // Primarily because the basket area will only receive mouseEnterEvent and mouveLeaveEvent. // It willn't be noticed of mouseMoveEvent, which would result in a apparently broken application state: bool underMouse = !qApp->activePopupWidget(); // if (qApp->activePopupWidget()) // underMouse = false; // Compute which note is hovered: Note *note = (m_isSelecting || !underMouse ? 0 : noteAt(pos)); Note::Zone zone = (note ? note->zoneAt(pos - QPointF(note->x(), note->y()), isDuringDrag()) : Note::None); // Inform the old and new hovered note (if any) and update the areas: doHoverEffects(note, zone, pos); } void BasketScene::mouseEnteredEditorWidget() { if (!m_lockedHovering && !qApp->activePopupWidget()) doHoverEffects(editedNote(), Note::Content, QPoint()); } void BasketScene::removeInserter() { if (m_inserterShown) { // Do not hide (and then update/repaint the view) if it is already hidden! m_inserterShown = false; invalidate(m_inserterRect); } } void BasketScene::placeInserter(Note *note, int zone) { // Remove the inserter: if (!note) { removeInserter(); return; } // Update the old position: if (inserterShown()) { invalidate(m_inserterRect); } // Some commodities: m_inserterShown = true; m_inserterTop = (zone == Note::TopGroup || zone == Note::TopInsert); m_inserterGroup = (zone == Note::TopGroup || zone == Note::BottomGroup); // X and width: qreal groupIndent = (note->isGroup() ? note->width() : Note::HANDLE_WIDTH); qreal x = note->x(); qreal width = (note->isGroup() ? note->rightLimit() - note->x() : note->width()); if (m_inserterGroup) { x += groupIndent; width -= groupIndent; } m_inserterSplit = (Settings::groupOnInsertionLine() && note && !note->isGroup() && !note->isFree() && !note->isColumn()); // if (note->isGroup()) // width = note->rightLimit() - note->x() - (m_inserterGroup ? groupIndent : 0); // Y: qreal y = note->y() - (m_inserterGroup && m_inserterTop ? 1 : 3); if (!m_inserterTop) y += (note->isColumn() ? note->height() : note->height()); // Assigning result: m_inserterRect = QRectF(x, y, width, 6 - (m_inserterGroup ? 2 : 0)); // Update the new position: invalidate(m_inserterRect); } inline void drawLineByRect(QPainter &painter, qreal x, qreal y, qreal width, qreal height) { painter.drawLine(x, y, x + width - 1, y + height - 1); } void BasketScene::drawInserter(QPainter &painter, qreal xPainter, qreal yPainter) { if (!m_inserterShown) return; QRectF rect = m_inserterRect; // For shorter code-lines when drawing! rect.translate(-xPainter, -yPainter); int lineY = (m_inserterGroup && m_inserterTop ? 0 : 2); int roundY = (m_inserterGroup && m_inserterTop ? 0 : 1); KStatefulBrush statefulBrush(KColorScheme::View, KColorScheme::HoverColor); const QColor highlightColor = palette().color(QPalette::Highlight).lighter(); painter.setPen(highlightColor); // The horizontal line: // painter.drawRect( rect.x(), rect.y() + lineY, rect.width(), 2); int width = rect.width() - 4; painter.fillRect(rect.x() + 2, rect.y() + lineY, width, 2, highlightColor); // The left-most and right-most edges (biggest vertical lines): drawLineByRect(painter, rect.x(), rect.y(), 1, (m_inserterGroup ? 4 : 6)); drawLineByRect(painter, rect.x() + rect.width() - 1, rect.y(), 1, (m_inserterGroup ? 4 : 6)); // The left and right mid vertical lines: drawLineByRect(painter, rect.x() + 1, rect.y() + roundY, 1, (m_inserterGroup ? 3 : 4)); drawLineByRect(painter, rect.x() + rect.width() - 2, rect.y() + roundY, 1, (m_inserterGroup ? 3 : 4)); // Draw the split as a feedback to know where is the limit between insert and group: if (m_inserterSplit) { int noteWidth = rect.width() + (m_inserterGroup ? Note::HANDLE_WIDTH : 0); int xSplit = rect.x() - (m_inserterGroup ? Note::HANDLE_WIDTH : 0) + noteWidth / 2; painter.drawRect(xSplit - 2, rect.y() + lineY, 4, 2); painter.drawRect(xSplit - 1, rect.y() + lineY, 2, 2); } } void BasketScene::helpEvent(QGraphicsSceneHelpEvent *event) { if (!m_loaded || !Settings::showNotesToolTip()) return; QString message; QRectF rect; QPointF contentPos = event->scenePos(); Note *note = noteAt(contentPos); if (!note && isFreeLayout()) { message = i18n("Insert note here\nRight click for more options"); QRectF itRect; for (QList::iterator it = m_blankAreas.begin(); it != m_blankAreas.end(); ++it) { itRect = QRectF(0, 0, m_view->viewport()->width(), m_view->viewport()->height()).intersected(*it); if (itRect.contains(contentPos)) { rect = itRect; rect.moveLeft(rect.left() - sceneRect().x()); rect.moveTop(rect.top() - sceneRect().y()); break; } } } else { if (!note) return; Note::Zone zone = note->zoneAt(contentPos - QPointF(note->x(), note->y())); switch (zone) { case Note::Resizer: message = (note->isColumn() ? i18n("Resize those columns") : (note->isGroup() ? i18n("Resize this group") : i18n("Resize this note"))); break; case Note::Handle: message = i18n("Select or move this note"); break; case Note::Group: message = i18n("Select or move this group"); break; case Note::TagsArrow: message = i18n("Assign or remove tags from this note"); if (note->states().count() > 0) { QString tagsString = ""; for (State::List::iterator it = note->states().begin(); it != note->states().end(); ++it) { QString tagName = "" + Tools::textToHTMLWithoutP((*it)->fullName()) + ""; if (tagsString.isEmpty()) tagsString = tagName; else tagsString = i18n("%1, %2", tagsString, tagName); } message = "" + message + "
" + i18n("Assigned Tags: %1", tagsString); } break; case Note::Custom0: message = note->content()->zoneTip(zone); break; //"Open this link/Open this file/Open this sound file/Launch this application" case Note::GroupExpander: message = (note->isFolded() ? i18n("Expand this group") : i18n("Collapse this group")); break; case Note::Link: case Note::Content: message = note->content()->editToolTipText(); break; case Note::TopInsert: case Note::BottomInsert: message = i18n("Insert note here\nRight click for more options"); break; case Note::TopGroup: message = i18n("Group note with the one below\nRight click for more options"); break; case Note::BottomGroup: message = i18n("Group note with the one above\nRight click for more options"); break; case Note::BottomColumn: message = i18n("Insert note here\nRight click for more options"); break; case Note::None: message = "** Zone NONE: internal error **"; break; default: if (zone >= Note::Emblem0) message = note->stateForEmblemNumber(zone - Note::Emblem0)->fullName(); else message = ""; break; } if (zone == Note::Content || zone == Note::Link || zone == Note::Custom0) { QStringList keys; QStringList values; note->content()->toolTipInfos(&keys, &values); keys.append(i18n("Added")); keys.append(i18n("Last Modification")); values.append(note->addedStringDate()); values.append(note->lastModificationStringDate()); message = "" + message; QStringList::iterator key; QStringList::iterator value; for (key = keys.begin(), value = values.begin(); key != keys.end() && value != values.end(); ++key, ++value) message += "
" + i18nc("of the form 'key: value'", "%1: %2", *key, *value); message += "
"; } else if (m_inserterSplit && (zone == Note::TopInsert || zone == Note::BottomInsert)) message += '\n' + i18n("Click on the right to group instead of insert"); else if (m_inserterSplit && (zone == Note::TopGroup || zone == Note::BottomGroup)) message += '\n' + i18n("Click on the left to insert instead of group"); rect = note->zoneRect(zone, contentPos - QPoint(note->x(), note->y())); rect.moveLeft(rect.left() - sceneRect().x()); rect.moveTop(rect.top() - sceneRect().y()); rect.moveLeft(rect.left() + note->x()); rect.moveTop(rect.top() + note->y()); } QToolTip::showText(event->screenPos(), message, m_view, rect.toRect()); } Note *BasketScene::lastNote() { Note *note = firstNote(); while (note && note->next()) note = note->next(); return note; } void BasketScene::deleteNotes() { Note *note = m_firstNote; while (note) { Note *tmp = note->next(); delete note; note = tmp; } m_firstNote = 0; m_resizingNote = 0; m_movingNote = 0; m_focusedNote = 0; m_startOfShiftSelectionNote = 0; m_tagPopupNote = 0; m_clickedToInsert = 0; m_savedClickedToInsert = 0; m_hoveredNote = 0; m_count = 0; m_countFounds = 0; m_countSelecteds = 0; emit resetStatusBarText(); emit countsChanged(this); } Note *BasketScene::noteAt(QPointF pos) { qreal x = pos.x(); qreal y = pos.y(); // NO: // // Do NOT check the bottom&right borders. // // Because imagine someone drag&drop a big note from the top to the bottom of a big basket (with big vertical scrollbars), // // the note is first removed, and relayoutNotes() compute the new height that is smaller // // Then noteAt() is called for the mouse pointer position, because the basket is now smaller, the cursor is out of boundaries!!! // // Should, of course, not return 0: if (x < 0 || x > sceneRect().width() || y < 0 || y > sceneRect().height()) return 0; // When resizing a note/group, keep it highlighted: if (m_resizingNote) return m_resizingNote; // Search and return the hovered note: Note *note = m_firstNote; Note *possibleNote; while (note) { possibleNote = note->noteAt(pos); if (possibleNote) { if (NoteDrag::selectedNotes.contains(possibleNote) || draggedNotes().contains(possibleNote)) return 0; else return possibleNote; } note = note->next(); } // If the basket is layouted in columns, return one of the columns to be able to add notes in them: if (isColumnsLayout()) { Note *column = m_firstNote; while (column) { if (x >= column->x() && x < column->rightLimit()) return column; column = column->next(); } } // Nothing found, no note is hovered: return NULL; } BasketScene::~BasketScene() { m_commitdelay.stop(); // we don't know how long deleteNotes() last so we want to make extra sure that nobody will commit in between if (m_decryptBox) delete m_decryptBox; #ifdef HAVE_LIBGPGME delete m_gpg; #endif deleteNotes(); if (m_view) delete m_view; } void BasketScene::animateLoad() { const int viewHeight = sceneRect().y() + m_view->viewport()->height(); QTime t = QTime::currentTime(); // Set random seed srand(t.hour() * 12 + t.minute() * 60 + t.second() * 60); bool needAnimation = false; m_animationTimeLine = new QTimeLine(ANIMATION_DELAY); m_animationTimeLine->setFrameRange(0, 100); connect(m_animationTimeLine, SIGNAL(frameChanged(int)), this, SLOT(animationFrameChanged(int))); connect(m_animationTimeLine, SIGNAL(finished()), this, SLOT(finishAnimation())); Note *note = firstNote(); while (note) { if ((note->y() < viewHeight) && note->matching()) { needAnimation |= note->initAnimationLoad(m_animationTimeLine); } note = note->next(); } if (needAnimation) { m_animationTimeLine->start(); } else { m_loaded = true; delete m_animationTimeLine; m_animationTimeLine = 0; } } void BasketScene::animationFrameChanged(int /*frame*/) { FOR_EACH_NOTE(note) note->unbufferizeAll(); if (!m_loaded) { m_loaded = true; update(); } } void BasketScene::finishAnimation() { m_animationTimeLine->deleteLater(); m_animationTimeLine = 0; FOR_EACH_NOTE(note) note->animationFinished(); update(); } QColor BasketScene::selectionRectInsideColor() { return Tools::mixColor(Tools::mixColor(backgroundColor(), palette().color(QPalette::Highlight)), backgroundColor()); } QColor alphaBlendColors(const QColor &bgColor, const QColor &fgColor, const int a) { // normal button... QRgb rgb = bgColor.rgb(); QRgb rgb_b = fgColor.rgb(); int alpha = a; if (alpha > 255) alpha = 255; if (alpha < 0) alpha = 0; int inv_alpha = 255 - alpha; QColor result = QColor(qRgb(qRed(rgb_b) * inv_alpha / 255 + qRed(rgb) * alpha / 255, qGreen(rgb_b) * inv_alpha / 255 + qGreen(rgb) * alpha / 255, qBlue(rgb_b) * inv_alpha / 255 + qBlue(rgb) * alpha / 255)); return result; } void BasketScene::unlock() { QTimer::singleShot(0, this, SLOT(load())); } void BasketScene::inactivityAutoLockTimeout() { lock(); } void BasketScene::drawBackground(QPainter *painter, const QRectF &rect) { if (!m_loadingLaunched) { if (!m_locked) { QTimer::singleShot(0, this, SLOT(load())); return; } else { Global::bnpView->notesStateChanged(); // Show "Locked" instead of "Loading..." in the statusbar } } if (!hasBackgroundImage()) { painter->fillRect(rect, backgroundColor()); // It's either a background pixmap to draw or a background color to fill: } else if (isTiledBackground() || (rect.x() < backgroundPixmap()->width() && rect.y() < backgroundPixmap()->height())) { painter->fillRect(rect, backgroundColor()); blendBackground(*painter, rect, 0, 0, /*opaque=*/true); } else { painter->fillRect(rect, backgroundColor()); } } void BasketScene::drawForeground(QPainter *painter, const QRectF &rect) { if (m_locked) { if (!m_decryptBox) { m_decryptBox = new QFrame(m_view); m_decryptBox->setFrameShape(QFrame::StyledPanel); m_decryptBox->setFrameShadow(QFrame::Plain); m_decryptBox->setLineWidth(1); QGridLayout *layout = new QGridLayout(m_decryptBox); layout->setContentsMargins(11, 11, 11, 11); layout->setSpacing(6); #ifdef HAVE_LIBGPGME m_button = new QPushButton(m_decryptBox); m_button->setText(i18n("&Unlock")); layout->addWidget(m_button, 1, 2); connect(m_button, SIGNAL(clicked()), this, SLOT(unlock())); #endif QLabel *label = new QLabel(m_decryptBox); QString text = "" + i18n("Password protected basket.") + "
"; #ifdef HAVE_LIBGPGME label->setText(text + i18n("Press Unlock to access it.")); #else label->setText(text + i18n("Encryption is not supported by
this version of %1.", QGuiApplication::applicationDisplayName())); #endif label->setAlignment(Qt::AlignTop); layout->addWidget(label, 0, 1, 1, 2); QLabel *pixmap = new QLabel(m_decryptBox); pixmap->setPixmap(KIconLoader::global()->loadIcon("encrypted", KIconLoader::NoGroup, KIconLoader::SizeHuge)); layout->addWidget(pixmap, 0, 0, 2, 1); QSpacerItem *spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); layout->addItem(spacer, 1, 1); label = new QLabel("" + i18n("To make baskets stay unlocked, change the automatic
" "locking duration in the application settings.") + "
", m_decryptBox); label->setAlignment(Qt::AlignTop); layout->addWidget(label, 2, 0, 1, 3); m_decryptBox->resize(layout->sizeHint()); } if (m_decryptBox->isHidden()) { m_decryptBox->show(); } #ifdef HAVE_LIBGPGME m_button->setFocus(); #endif m_decryptBox->move((m_view->viewport()->width() - m_decryptBox->width()) / 2, (m_view->viewport()->height() - m_decryptBox->height()) / 2); } else { if (m_decryptBox && !m_decryptBox->isHidden()) m_decryptBox->hide(); } if (!m_loaded) { setSceneRect(0, 0, m_view->viewport()->width(), m_view->viewport()->height()); QBrush brush(backgroundColor()); QPixmap pixmap(m_view->viewport()->width(), m_view->viewport()->height()); // TODO: Clip it to asked size only! QPainter painter2(&pixmap); QTextDocument rt; rt.setHtml(QString("
%1
").arg(i18n("Loading..."))); rt.setTextWidth(m_view->viewport()->width()); int hrt = rt.size().height(); painter2.fillRect(0, 0, m_view->viewport()->width(), m_view->viewport()->height(), brush); blendBackground(painter2, QRectF(0, 0, m_view->viewport()->width(), m_view->viewport()->height()), -1, -1, /*opaque=*/true); QPalette pal = palette(); pal.setColor(QPalette::WindowText, textColor()); painter2.translate(0, (m_view->viewport()->height() - hrt) / 2); QAbstractTextDocumentLayout::PaintContext context; context.palette = pal; rt.documentLayout()->draw(&painter2, context); painter2.end(); painter->drawPixmap(0, 0, pixmap); return; // TODO: Clip to the wanted rectangle } enableActions(); if ((inserterShown() && rect.intersects(inserterRect())) || (m_isSelecting && rect.intersects(m_selectionRect))) { // Draw inserter: if (inserterShown() && rect.intersects(inserterRect())) { drawInserter(*painter, 0, 0); } // Draw selection rect: if (m_isSelecting && rect.intersects(m_selectionRect)) { QRectF selectionRect = m_selectionRect; QRectF selectionRectInside(selectionRect.x() + 1, selectionRect.y() + 1, selectionRect.width() - 2, selectionRect.height() - 2); if (selectionRectInside.width() > 0 && selectionRectInside.height() > 0) { QColor insideColor = selectionRectInsideColor(); painter->fillRect(selectionRectInside, QBrush(insideColor, Qt::Dense4Pattern)); } painter->setPen(palette().color(QPalette::Highlight).darker()); painter->drawRect(selectionRect); painter->setPen(Tools::mixColor(palette().color(QPalette::Highlight).darker(), backgroundColor())); painter->drawPoint(selectionRect.topLeft()); painter->drawPoint(selectionRect.topRight()); painter->drawPoint(selectionRect.bottomLeft()); painter->drawPoint(selectionRect.bottomRight()); } } } /* rect(x,y,width,height)==(xBackgroundToDraw,yBackgroundToDraw,widthToDraw,heightToDraw) */ void BasketScene::blendBackground(QPainter &painter, const QRectF &rect, qreal xPainter, qreal yPainter, bool opaque, QPixmap *bg) { painter.save(); if (xPainter == -1 && yPainter == -1) { xPainter = rect.x(); yPainter = rect.y(); } if (hasBackgroundImage()) { const QPixmap *bgPixmap = (bg ? /* * */ bg : (opaque ? m_opaqueBackgroundPixmap : m_backgroundPixmap)); if (isTiledBackground()) { painter.drawTiledPixmap(rect.x() - xPainter, rect.y() - yPainter, rect.width(), rect.height(), *bgPixmap, rect.x(), rect.y()); } else { painter.drawPixmap(QPointF(rect.x() - xPainter, rect.y() - yPainter), *bgPixmap, rect); } } painter.restore(); } void BasketScene::recomputeBlankRects() { m_blankAreas.clear(); return; m_blankAreas.append(QRectF(0, 0, sceneRect().width(), sceneRect().height())); FOR_EACH_NOTE(note) note->recomputeBlankRects(m_blankAreas); // See the drawing of blank areas in BasketScene::drawContents() if (hasBackgroundImage() && !isTiledBackground()) substractRectOnAreas(QRectF(0, 0, backgroundPixmap()->width(), backgroundPixmap()->height()), m_blankAreas, false); } void BasketScene::unsetNotesWidth() { Note *note = m_firstNote; while (note) { note->unsetWidth(); note = note->next(); } } void BasketScene::relayoutNotes(bool animate) { if (Global::bnpView->currentBasket() != this) return; // Optimize load time, and basket will be relaid out when activated, anyway if (!Settings::playAnimations()) animate = false; int h = 0; tmpWidth = 0; tmpHeight = 0; Note *note = m_firstNote; while (note) { if (note->matching()) { note->relayoutAt(0, h, animate); if (note->hasResizer()) { int minGroupWidth = note->minRight() - note->x(); if (note->groupWidth() < minGroupWidth) { note->setGroupWidth(minGroupWidth); relayoutNotes(animate); // Redo the thing, but this time it should not recurse return; } } h += note->height(); } note = note->next(); } if (isFreeLayout()) tmpHeight += 100; else tmpHeight += 15; setSceneRect(0, 0, qMax((qreal)m_view->viewport()->width(), tmpWidth), qMax((qreal)m_view->viewport()->height(), tmpHeight)); recomputeBlankRects(); placeEditor(); doHoverEffects(); invalidate(); } void BasketScene::popupEmblemMenu(Note *note, int emblemNumber) { m_tagPopupNote = note; State *state = note->stateForEmblemNumber(emblemNumber); State *nextState = state->nextState(/*cycle=*/false); Tag *tag = state->parentTag(); m_tagPopup = tag; QKeySequence sequence = tag->shortcut(); bool sequenceOnDelete = (nextState == 0 && !tag->shortcut().isEmpty()); QMenu menu(m_view); if (tag->countStates() == 1) { menu.addSection(/*SmallIcon(state->icon()), */ tag->name()); QAction *act; act = new QAction(QIcon::fromTheme("edit-delete"), i18n("&Remove"), &menu); act->setData(1); menu.addAction(act); act = new QAction(QIcon::fromTheme("configure"), i18n("&Customize..."), &menu); act->setData(2); menu.addAction(act); menu.addSeparator(); act = new QAction(QIcon::fromTheme("search-filter"), i18n("&Filter by this Tag"), &menu); act->setData(3); menu.addAction(act); } else { menu.addSection(tag->name()); QList::iterator it; State *currentState; int i = 10; // QActionGroup makes the actions exclusive; turns checkboxes into radio // buttons on some styles. QActionGroup *emblemGroup = new QActionGroup(&menu); for (it = tag->states().begin(); it != tag->states().end(); ++it) { currentState = *it; QKeySequence sequence; if (currentState == nextState && !tag->shortcut().isEmpty()) sequence = tag->shortcut(); StateAction *sa = new StateAction(currentState, QKeySequence(sequence), 0, false); sa->setChecked(state == currentState); sa->setActionGroup(emblemGroup); sa->setData(i); menu.addAction(sa); if (currentState == nextState && !tag->shortcut().isEmpty()) sa->setShortcut(sequence); ++i; } menu.addSeparator(); QAction *act = new QAction(&menu); act->setIcon(QIcon::fromTheme("edit-delete")); act->setText(i18n("&Remove")); act->setShortcut(sequenceOnDelete ? sequence : QKeySequence()); act->setData(1); menu.addAction(act); act = new QAction(QIcon::fromTheme("configure"), i18n("&Customize..."), &menu); act->setData(2); menu.addAction(act); menu.addSeparator(); act = new QAction(QIcon::fromTheme("search-filter"), i18n("&Filter by this Tag"), &menu); act->setData(3); menu.addAction(act); act = new QAction(QIcon::fromTheme("search-filter"), i18n("Filter by this &State"), &menu); act->setData(4); menu.addAction(act); } connect(&menu, SIGNAL(triggered(QAction *)), this, SLOT(toggledStateInMenu(QAction *))); connect(&menu, SIGNAL(aboutToHide()), this, SLOT(unlockHovering())); connect(&menu, SIGNAL(aboutToHide()), this, SLOT(disableNextClick())); m_lockedHovering = true; menu.exec(QCursor::pos()); } void BasketScene::toggledStateInMenu(QAction *action) { int id = action->data().toInt(); if (id == 1) { removeTagFromSelectedNotes(m_tagPopup); // m_tagPopupNote->removeTag(m_tagPopup); // m_tagPopupNote->setWidth(0); // To force a new layout computation updateEditorAppearance(); filterAgain(); save(); return; } if (id == 2) { // Customize this State: TagsEditDialog dialog(m_view, m_tagPopupNote->stateOfTag(m_tagPopup)); dialog.exec(); return; } if (id == 3) { // Filter by this Tag decoration()->filterBar()->filterTag(m_tagPopup); return; } if (id == 4) { // Filter by this State decoration()->filterBar()->filterState(m_tagPopupNote->stateOfTag(m_tagPopup)); return; } /*addStateToSelectedNotes*/ changeStateOfSelectedNotes(m_tagPopup->states()[id - 10] /*, orReplace=true*/); // m_tagPopupNote->addState(m_tagPopup->states()[id - 10], true); filterAgain(); save(); } State *BasketScene::stateForTagFromSelectedNotes(Tag *tag) { State *state = 0; FOR_EACH_NOTE(note) if (note->stateForTagFromSelectedNotes(tag, &state) && state == 0) return 0; return state; } void BasketScene::activatedTagShortcut(Tag *tag) { // Compute the next state to set: State *state = stateForTagFromSelectedNotes(tag); if (state) state = state->nextState(/*cycle=*/false); else state = tag->states().first(); // Set or unset it: if (state) { FOR_EACH_NOTE(note) note->addStateToSelectedNotes(state, /*orReplace=*/true); updateEditorAppearance(); } else removeTagFromSelectedNotes(tag); filterAgain(); save(); } void BasketScene::popupTagsMenu(Note *note) { m_tagPopupNote = note; QMenu menu(m_view); menu.addSection(i18n("Tags")); Global::bnpView->populateTagsMenu(menu, note); m_lockedHovering = true; menu.exec(QCursor::pos()); } void BasketScene::unlockHovering() { m_lockedHovering = false; doHoverEffects(); } void BasketScene::toggledTagInMenu(QAction *act) { int id = act->data().toInt(); if (id == 1) { // Assign new Tag... TagsEditDialog dialog(m_view, /*stateToEdit=*/0, /*addNewTag=*/true); dialog.exec(); if (!dialog.addedStates().isEmpty()) { State::List states = dialog.addedStates(); for (State::List::iterator itState = states.begin(); itState != states.end(); ++itState) FOR_EACH_NOTE(note) note->addStateToSelectedNotes(*itState); updateEditorAppearance(); filterAgain(); save(); } return; } if (id == 2) { // Remove All removeAllTagsFromSelectedNotes(); filterAgain(); save(); return; } if (id == 3) { // Customize... TagsEditDialog dialog(m_view); dialog.exec(); return; } Tag *tag = Tag::all[id - 10]; if (!tag) return; if (m_tagPopupNote->hasTag(tag)) removeTagFromSelectedNotes(tag); else addTagToSelectedNotes(tag); m_tagPopupNote->setWidth(0); // To force a new layout computation filterAgain(); save(); } void BasketScene::addTagToSelectedNotes(Tag *tag) { FOR_EACH_NOTE(note) note->addTagToSelectedNotes(tag); updateEditorAppearance(); } void BasketScene::removeTagFromSelectedNotes(Tag *tag) { FOR_EACH_NOTE(note) note->removeTagFromSelectedNotes(tag); updateEditorAppearance(); } void BasketScene::addStateToSelectedNotes(State *state) { FOR_EACH_NOTE(note) note->addStateToSelectedNotes(state); updateEditorAppearance(); } void BasketScene::updateEditorAppearance() { if (isDuringEdit() && m_editor->graphicsWidget()) { m_editor->graphicsWidget()->setFont(m_editor->note()->font()); if (m_editor->graphicsWidget()->widget()) { QPalette palette; palette.setColor(m_editor->graphicsWidget()->widget()->backgroundRole(), m_editor->note()->backgroundColor()); palette.setColor(m_editor->graphicsWidget()->widget()->foregroundRole(), m_editor->note()->textColor()); m_editor->graphicsWidget()->setPalette(palette); } // Ugly Hack around Qt bugs: placeCursor() don't call any signal: HtmlEditor *htmlEditor = dynamic_cast(m_editor); if (htmlEditor) { if (m_editor->textEdit()->textCursor().atStart()) { m_editor->textEdit()->moveCursor(QTextCursor::Right); m_editor->textEdit()->moveCursor(QTextCursor::Left); } else { m_editor->textEdit()->moveCursor(QTextCursor::Left); m_editor->textEdit()->moveCursor(QTextCursor::Right); } htmlEditor->cursorPositionChanged(); // Does not work anyway :-( (when clicking on a red bold text, the toolbar still show black normal text) } } } void BasketScene::editorPropertiesChanged() { if (isDuringEdit() && m_editor->note()->content()->type() == NoteType::Html) { m_editor->textEdit()->setAutoFormatting(Settings::autoBullet() ? QTextEdit::AutoAll : QTextEdit::AutoNone); } } void BasketScene::changeStateOfSelectedNotes(State *state) { FOR_EACH_NOTE(note) note->changeStateOfSelectedNotes(state); updateEditorAppearance(); } void BasketScene::removeAllTagsFromSelectedNotes() { FOR_EACH_NOTE(note) note->removeAllTagsFromSelectedNotes(); updateEditorAppearance(); } bool BasketScene::selectedNotesHaveTags() { FOR_EACH_NOTE(note) if (note->selectedNotesHaveTags()) return true; return false; } QColor BasketScene::backgroundColor() const { if (m_backgroundColorSetting.isValid()) return m_backgroundColorSetting; else return palette().color(QPalette::Base); } QColor BasketScene::textColor() const { if (m_textColorSetting.isValid()) return m_textColorSetting; else return palette().color(QPalette::Text); } void BasketScene::unbufferizeAll() { FOR_EACH_NOTE(note) note->unbufferizeAll(); } Note *BasketScene::editedNote() { if (m_editor) return m_editor->note(); else return 0; } bool BasketScene::hasTextInEditor() { if (!isDuringEdit() || !redirectEditActions()) return false; if (m_editor->textEdit()) return !m_editor->textEdit()->document()->isEmpty(); else if (m_editor->lineEdit()) return !m_editor->lineEdit()->displayText().isEmpty(); else return false; } bool BasketScene::hasSelectedTextInEditor() { if (!isDuringEdit() || !redirectEditActions()) return false; if (m_editor->textEdit()) { // The following line does NOT work if one letter is selected and the user press Shift+Left or Shift+Right to unselect than letter: // Qt mysteriously tell us there is an invisible selection!! // return m_editor->textEdit()->hasSelectedText(); return !m_editor->textEdit()->textCursor().selectedText().isEmpty(); } else if (m_editor->lineEdit()) return m_editor->lineEdit()->hasSelectedText(); else return false; } bool BasketScene::selectedAllTextInEditor() { if (!isDuringEdit() || !redirectEditActions()) return false; if (m_editor->textEdit()) { return m_editor->textEdit()->document()->isEmpty() || m_editor->textEdit()->toPlainText() == m_editor->textEdit()->textCursor().selectedText(); } else if (m_editor->lineEdit()) return m_editor->lineEdit()->displayText().isEmpty() || m_editor->lineEdit()->displayText() == m_editor->lineEdit()->selectedText(); else return false; } void BasketScene::selectionChangedInEditor() { Global::bnpView->notesStateChanged(); } void BasketScene::contentChangedInEditor() { // Do not wait 3 seconds, because we need the note to expand as needed (if a line is too wider... the note should grow wider): if (m_editor->textEdit()) m_editor->autoSave(/*toFileToo=*/false); // else { if (m_inactivityAutoSaveTimer.isActive()) m_inactivityAutoSaveTimer.stop(); m_inactivityAutoSaveTimer.setSingleShot(true); m_inactivityAutoSaveTimer.start(3 * 1000); Global::bnpView->setUnsavedStatus(true); // } } void BasketScene::inactivityAutoSaveTimeout() { if (m_editor) m_editor->autoSave(/*toFileToo=*/true); } void BasketScene::placeEditorAndEnsureVisible() { placeEditor(/*andEnsureVisible=*/true); } // TODO: [kw] Oh boy, this will probably require some tweaking. void BasketScene::placeEditor(bool /*andEnsureVisible*/ /*= false*/) { if (!isDuringEdit()) return; QFrame *editorQFrame = dynamic_cast(m_editor->graphicsWidget()->widget()); KTextEdit *textEdit = m_editor->textEdit(); Note *note = m_editor->note(); qreal frameWidth = (editorQFrame ? editorQFrame->frameWidth() : 0); qreal x = note->x() + note->contentX() + note->content()->xEditorIndent() - frameWidth; qreal y; qreal maxHeight = qMax((qreal)m_view->viewport()->height(), sceneRect().height()); qreal height, width; if (textEdit) { // Need to do it 2 times, because it's wrong overwise // (sometimes, width depends on height, and sometimes, height depends on with): for (int i = 0; i < 2; i++) { // FIXME: CRASH: Select all text, press Del or [<--] and editor->removeSelectedText() is called: // editor->sync() CRASH!! // editor->sync(); y = note->y() + Note::NOTE_MARGIN - frameWidth; height = note->height() - 2 * frameWidth - 2 * Note::NOTE_MARGIN; width = note->x() + note->width() - x + 1; if (y + height > maxHeight) y = maxHeight - height; m_editor->graphicsWidget()->setMaximumSize(width, height); textEdit->setFixedSize(width, height); textEdit->viewport()->setFixedSize(width, height); } } else { height = note->height() - 2 * Note::NOTE_MARGIN + 2 * frameWidth; width = note->x() + note->width() - x; // note->rightLimit() - x + 2*m_view->frameWidth; if (m_editor->graphicsWidget()) m_editor->graphicsWidget()->widget()->setFixedSize(width, height); x -= 1; y = note->y() + Note::NOTE_MARGIN - frameWidth; } if ((m_editorWidth > 0 && m_editorWidth != width) || (m_editorHeight > 0 && m_editorHeight != height)) { m_editorWidth = width; // Avoid infinite recursion!!! m_editorHeight = height; m_editor->autoSave(/*toFileToo=*/true); } m_editorWidth = width; m_editorHeight = height; m_editor->graphicsWidget()->setPos(x, y); m_editorX = x; m_editorY = y; // if (andEnsureVisible) // ensureNoteVisible(note); } void BasketScene::editorCursorPositionChanged() { if (!isDuringEdit()) return; FocusedTextEdit *textEdit = dynamic_cast(m_editor->textEdit()); if (textEdit) { QPoint cursorPoint = textEdit->viewport()->mapToGlobal(textEdit->cursorRect().center()); // QPointF contentsCursor = m_view->mapToScene( m_view->viewport()->mapFromGlobal(cursorPoint) ); // m_view->ensureVisible(contentsCursor.x(), contentsCursor.y(),1,1); } } void BasketScene::closeEditorDelayed() { setFocus(); QTimer::singleShot(0, this, SLOT(closeEditor())); } bool BasketScene::closeEditor(bool deleteEmptyNote /* =true*/) { if (!isDuringEdit()) return true; if (m_doNotCloseEditor) return true; if (m_redirectEditActions) { if (m_editor->textEdit()) { disconnect(m_editor->textEdit(), SIGNAL(selectionChanged()), this, SLOT(selectionChangedInEditor())); disconnect(m_editor->textEdit(), SIGNAL(textChanged()), this, SLOT(selectionChangedInEditor())); disconnect(m_editor->textEdit(), SIGNAL(textChanged()), this, SLOT(contentChangedInEditor())); } else if (m_editor->lineEdit()) { disconnect(m_editor->lineEdit(), SIGNAL(selectionChanged()), this, SLOT(selectionChangedInEditor())); disconnect(m_editor->lineEdit(), SIGNAL(textChanged(const QString &)), this, SLOT(selectionChangedInEditor())); disconnect(m_editor->lineEdit(), SIGNAL(textChanged(const QString &)), this, SLOT(contentChangedInEditor())); } } m_editorTrackMouseEvent = false; m_editor->graphicsWidget()->widget()->disconnect(); removeItem(m_editor->graphicsWidget()); m_editor->validate(); Note *note = m_editor->note(); // Delete the editor BEFORE unselecting the note because unselecting the note would trigger closeEditor() recursivly: bool isEmpty = m_editor->isEmpty(); delete m_editor; m_editor = 0; m_redirectEditActions = false; m_editorWidth = -1; m_editorHeight = -1; m_inactivityAutoSaveTimer.stop(); // Delete the note if it is now empty: if (isEmpty && deleteEmptyNote) { focusANonSelectedNoteAboveOrThenBelow(); note->setSelected(true); note->deleteSelectedNotes(); if (m_hoveredNote == note) m_hoveredNote = 0; if (m_focusedNote == note) m_focusedNote = 0; delete note; save(); note = 0; } unlockHovering(); filterAgain(/*andEnsureVisible=*/false); // Does not work: // if (Settings::playAnimations()) // note->setOnTop(true); // So if it grew, do not obscure it temporarily while the notes below it are moving if (note) note->setSelected(false); // unselectAll(); doHoverEffects(); // save(); Global::bnpView->m_actEditNote->setEnabled(!isLocked() && countSelecteds() == 1 /*&& !isDuringEdit()*/); emit resetStatusBarText(); // Remove the "Editing. ... to validate." text. // if (qApp->activeWindow() == Global::mainContainer) // Set focus to the basket, unless the user pressed a letter key in the filter bar and the currently edited note came hidden, then editing closed: if (!decoration()->filterBar()->lineEdit()->hasFocus()) setFocus(); // Return true if the note is still there: return (note != 0); } void BasketScene::closeBasket() { closeEditor(); unbufferizeAll(); // Keep the memory footprint low if (isEncrypted()) { if (Settings::enableReLockTimeout()) { int seconds = Settings::reLockTimeoutMinutes() * 60; m_inactivityAutoLockTimer.setSingleShot(true); m_inactivityAutoLockTimer.start(seconds * 1000); } } } void BasketScene::openBasket() { if (m_inactivityAutoLockTimer.isActive()) m_inactivityAutoLockTimer.stop(); } Note *BasketScene::theSelectedNote() { if (countSelecteds() != 1) { qDebug() << "NO SELECTED NOTE !!!!"; return 0; } Note *selectedOne; FOR_EACH_NOTE(note) { selectedOne = note->theSelectedNote(); if (selectedOne) return selectedOne; } qDebug() << "One selected note, BUT NOT FOUND !!!!"; return 0; } NoteSelection *BasketScene::selectedNotes() { NoteSelection selection; FOR_EACH_NOTE(note) selection.append(note->selectedNotes()); if (!selection.firstChild) return 0; for (NoteSelection *node = selection.firstChild; node; node = node->next) node->parent = 0; // If the top-most groups are columns, export only children of those groups // (because user is not aware that columns are groups, and don't care: it's not what she want): if (selection.firstChild->note->isColumn()) { NoteSelection tmpSelection; NoteSelection *nextNode; NoteSelection *nextSubNode; for (NoteSelection *node = selection.firstChild; node; node = nextNode) { nextNode = node->next; if (node->note->isColumn()) { for (NoteSelection *subNode = node->firstChild; subNode; subNode = nextSubNode) { nextSubNode = subNode->next; tmpSelection.append(subNode); subNode->parent = 0; subNode->next = 0; } } else { tmpSelection.append(node); node->parent = 0; node->next = 0; } } // debugSel(tmpSelection.firstChild); return tmpSelection.firstChild; } else { // debugSel(selection.firstChild); return selection.firstChild; } } void BasketScene::showEditedNoteWhileFiltering() { if (m_editor) { Note *note = m_editor->note(); filterAgain(); note->setSelected(true); relayoutNotes(false); note->setX(note->x()); note->setY(note->y()); filterAgainDelayed(); } } void BasketScene::noteEdit(Note *note, bool justAdded, const QPointF &clickedPoint) // TODO: Remove the first parameter!!! { if (!note) note = theSelectedNote(); // TODO: Or pick the focused note! if (!note) return; if (isDuringEdit()) { closeEditor(); // Validate the noteeditors in QLineEdit that does not intercept Enter key press (and edit is triggered with Enter too... Can conflict) return; } if (note != m_focusedNote) { setFocusedNote(note); m_startOfShiftSelectionNote = note; } if (justAdded && isFiltering()) { QTimer::singleShot(0, this, SLOT(showEditedNoteWhileFiltering())); } doHoverEffects(note, Note::Content); // Be sure (in the case Edit was triggered by menu or Enter key...): better feedback! NoteEditor *editor = NoteEditor::editNoteContent(note->content(), 0); if (editor->graphicsWidget()) { m_editor = editor; addItem(m_editor->graphicsWidget()); placeEditorAndEnsureVisible(); // placeEditor(); // FIXME: After? m_redirectEditActions = m_editor->lineEdit() || m_editor->textEdit(); if (m_redirectEditActions) { // In case there is NO text, "Select All" is disabled. But if the user press a key the there is now a text: // selection has not changed but "Select All" should be re-enabled: m_editor->connectActions(this); } m_editor->graphicsWidget()->setFocus(); connect(m_editor, SIGNAL(askValidation()), this, SLOT(closeEditorDelayed())); connect(m_editor, SIGNAL(mouseEnteredEditorWidget()), this, SLOT(mouseEnteredEditorWidget())); if (clickedPoint != QPoint()) { m_editor->setCursorTo(clickedPoint); updateEditorAppearance(); } // qApp->processEvents(); // Show the editor toolbar before ensuring the note is visible ensureNoteVisible(note); // because toolbar can create a new line and then partially hide the note m_editor->graphicsWidget()->setFocus(); // When clicking in the basket, a QTimer::singleShot(0, ...) focus the basket! So we focus the widget after qApp->processEvents() emit resetStatusBarText(); // Display "Editing. ... to validate." } else { // Delete the note user have canceled the addition: if ((justAdded && editor->canceled()) || editor->isEmpty() /*) && editor->note()->states().count() <= 0*/) { focusANonSelectedNoteAboveOrThenBelow(); editor->note()->setSelected(true); editor->note()->deleteSelectedNotes(); if (m_hoveredNote == editor->note()) m_hoveredNote = 0; if (m_focusedNote == editor->note()) m_focusedNote = 0; delete editor->note(); save(); } editor->deleteLater(); unlockHovering(); filterAgain(); unselectAll(); } // Must set focus to the editor, otherwise edit cursor is not seen and precomposed characters cannot be entered if (m_editor != NULL && m_editor->textEdit() != NULL) m_editor->textEdit()->setFocus(); Global::bnpView->m_actEditNote->setEnabled(false); } void BasketScene::noteDelete() { if (redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->textCursor().deleteChar(); else if (m_editor->lineEdit()) m_editor->lineEdit()->del(); return; } if (countSelecteds() <= 0) return; int really = KMessageBox::Yes; if (Settings::confirmNoteDeletion()) really = KMessageBox::questionYesNo(m_view, i18np("Do you really want to delete this note?", "Do you really want to delete these %1 notes?", countSelecteds()), i18np("Delete Note", "Delete Notes", countSelecteds()), KStandardGuiItem::del(), KStandardGuiItem::cancel()); if (really == KMessageBox::No) return; noteDeleteWithoutConfirmation(); } void BasketScene::focusANonSelectedNoteBelow(bool inSameColumn) { // First focus another unselected one below it...: if (m_focusedNote && m_focusedNote->isSelected()) { Note *next = m_focusedNote->nextShownInStack(); while (next && next->isSelected()) next = next->nextShownInStack(); if (next) { if (inSameColumn && isColumnsLayout() && m_focusedNote->parentPrimaryNote() == next->parentPrimaryNote()) { setFocusedNote(next); m_startOfShiftSelectionNote = next; } } } } void BasketScene::focusANonSelectedNoteAbove(bool inSameColumn) { // ... Or above it: if (m_focusedNote && m_focusedNote->isSelected()) { Note *prev = m_focusedNote->prevShownInStack(); while (prev && prev->isSelected()) prev = prev->prevShownInStack(); if (prev) { if (inSameColumn && isColumnsLayout() && m_focusedNote->parentPrimaryNote() == prev->parentPrimaryNote()) { setFocusedNote(prev); m_startOfShiftSelectionNote = prev; } } } } void BasketScene::focusANonSelectedNoteBelowOrThenAbove() { focusANonSelectedNoteBelow(/*inSameColumn=*/true); focusANonSelectedNoteAbove(/*inSameColumn=*/true); focusANonSelectedNoteBelow(/*inSameColumn=*/false); focusANonSelectedNoteAbove(/*inSameColumn=*/false); } void BasketScene::focusANonSelectedNoteAboveOrThenBelow() { focusANonSelectedNoteAbove(/*inSameColumn=*/true); focusANonSelectedNoteBelow(/*inSameColumn=*/true); focusANonSelectedNoteAbove(/*inSameColumn=*/false); focusANonSelectedNoteBelow(/*inSameColumn=*/false); } void BasketScene::noteDeleteWithoutConfirmation(bool deleteFilesToo) { // If the currently focused note is selected, it will be deleted. focusANonSelectedNoteBelowOrThenAbove(); // Do the deletion: Note *note = firstNote(); Note *next; while (note) { next = note->next(); // If we delete 'note' on the next line, note->next() will be 0! note->deleteSelectedNotes(deleteFilesToo, &m_notesToBeDeleted); note = next; } if (!m_notesToBeDeleted.isEmpty()) { doCleanUp(); } relayoutNotes(true); // FIXME: filterAgain()? save(); } void BasketScene::doCopy(CopyMode copyMode) { QClipboard *cb = QApplication::clipboard(); QClipboard::Mode mode = ((copyMode == CopyToSelection) ? QClipboard::Selection : QClipboard::Clipboard); NoteSelection *selection = selectedNotes(); int countCopied = countSelecteds(); if (selection->firstStacked()) { QDrag *d = NoteDrag::dragObject(selection, copyMode == CutToClipboard, /*source=*/0); // d will be deleted by QT // /*bool shouldRemove = */d->drag(); // delete selection; cb->setMimeData(d->mimeData(), mode); // NoteMultipleDrag will be deleted by QT // if (copyMode == CutToClipboard && !note->useFile()) // If useFile(), NoteDrag::dragObject() will delete it TODO // note->slotDelete(); if (copyMode == CutToClipboard) { noteDeleteWithoutConfirmation(/*deleteFilesToo=*/false); focusANote(); } switch (copyMode) { default: case CopyToClipboard: emit postMessage(i18np("Copied note to clipboard.", "Copied notes to clipboard.", countCopied)); break; case CutToClipboard: emit postMessage(i18np("Cut note to clipboard.", "Cut notes to clipboard.", countCopied)); break; case CopyToSelection: emit postMessage(i18np("Copied note to selection.", "Copied notes to selection.", countCopied)); break; } } } void BasketScene::noteCopy() { if (redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->copy(); else if (m_editor->lineEdit()) m_editor->lineEdit()->copy(); } else doCopy(CopyToClipboard); } void BasketScene::noteCut() { if (redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->cut(); else if (m_editor->lineEdit()) m_editor->lineEdit()->cut(); } else doCopy(CutToClipboard); } void BasketScene::noteOpen(Note *note) { /* GetSelectedNotes NoSelectedNote || Count == 0 ? return AllTheSameType ? Get { url, message(count) } */ // TODO: Open ALL selected notes! if (!note) note = theSelectedNote(); if (!note) return; QUrl url = note->content()->urlToOpen(/*with=*/false); QString message = note->content()->messageWhenOpening(NoteContent::OpenOne /*NoteContent::OpenSeveral*/); if (url.isEmpty()) { if (message.isEmpty()) emit postMessage(i18n("Unable to open this note.") /*"Unable to open those notes."*/); else { int result = KMessageBox::warningContinueCancel(m_view, message, /*caption=*/QString(), KGuiItem(i18n("&Edit"), "edit")); if (result == KMessageBox::Continue) noteEdit(note); } } else { emit postMessage(message); // "Opening link target..." / "Launching application..." / "Opening note file..." // Finally do the opening job: QString customCommand = note->content()->customOpenCommand(); if (url.url().startsWith("basket://")) { emit crossReference(url.url()); } else if (customCommand.isEmpty()) { KRun *run = new KRun(url, m_view->window()); run->setAutoDelete(true); } else { QList urls {url}; KRun::run(customCommand, urls, m_view->window()); } } } /** Code from bool KRun::displayOpenWithDialog(const KUrl::List& lst, bool tempFiles) * It does not allow to set a text, so I ripped it to do that: */ bool KRun__displayOpenWithDialog(const QList &lst, QWidget *window, bool tempFiles, const QString &text) { if (qApp && !KAuthorized::authorizeKAction("openwith")) { KMessageBox::sorry(window, i18n("You are not authorized to open this file.")); // TODO: Better message, i18n freeze :-( return false; } KOpenWithDialog l(lst, text, QString(), 0L); if (l.exec()) { KService::Ptr service = l.service(); if (!!service) return KRun::run(*service, lst, window, tempFiles); // qDebug(250) << "No service set, running " << l.text() << endl; return KRun::run(l.text(), lst, window); // TODO handle tempFiles } return false; } void BasketScene::noteOpenWith(Note *note) { if (!note) note = theSelectedNote(); if (!note) return; QUrl url = note->content()->urlToOpen(/*with=*/true); QString message = note->content()->messageWhenOpening(NoteContent::OpenOneWith /*NoteContent::OpenSeveralWith*/); QString text = note->content()->messageWhenOpening(NoteContent::OpenOneWithDialog /*NoteContent::OpenSeveralWithDialog*/); if (url.isEmpty()) { emit postMessage(i18n("Unable to open this note.") /*"Unable to open those notes."*/); } else { QList urls {url}; if (KRun__displayOpenWithDialog(urls, m_view->window(), false, text)) emit postMessage(message); // "Opening link target with..." / "Opening note file with..." } } void BasketScene::noteSaveAs() { // if (!note) // note = theSelectedNote(); Note *note = theSelectedNote(); if (!note) return; QUrl url = note->content()->urlToOpen(/*with=*/false); if (url.isEmpty()) return; QString fileName = QFileDialog::getSaveFileName(m_view, i18n("Save to File"), url.fileName(), note->content()->saveAsFilters()); // TODO: Ask to overwrite ! if (fileName.isEmpty()) return; // TODO: Convert format, etc. (use NoteContent::saveAs(fileName)) KIO::copy(url, QUrl::fromLocalFile(fileName)); } Note *BasketScene::selectedGroup() { FOR_EACH_NOTE(note) { Note *selectedGroup = note->selectedGroup(); if (selectedGroup) { // If the selected group is one group in a column, then return that group, and not the column, // because the column is not ungrouppage, and the Ungroup action would be disabled. if (selectedGroup->isColumn() && selectedGroup->firstChild() && !selectedGroup->firstChild()->next()) { return selectedGroup->firstChild(); } return selectedGroup; } } return 0; } bool BasketScene::selectionIsOneGroup() { return (selectedGroup() != 0); } Note *BasketScene::firstSelected() { Note *first = 0; FOR_EACH_NOTE(note) { first = note->firstSelected(); if (first) return first; } return 0; } Note *BasketScene::lastSelected() { Note *last = 0, *tmp = 0; FOR_EACH_NOTE(note) { tmp = note->lastSelected(); if (tmp) last = tmp; } return last; } bool BasketScene::convertTexts() { m_watcher->stopScan(); bool convertedNotes = false; if (!isLoaded()) load(); FOR_EACH_NOTE(note) if (note->convertTexts()) convertedNotes = true; if (convertedNotes) save(); m_watcher->startScan(); return convertedNotes; } void BasketScene::noteGroup() { /* // Nothing to do? if (isLocked() || countSelecteds() <= 1) return; // If every selected notes are ALREADY in one group, then don't touch anything: Note *selectedGroup = this->selectedGroup(); if (selectedGroup && !selectedGroup->isColumn()) return; */ // Copied from BNPView::updateNotesActions() bool severalSelected = countSelecteds() >= 2; Note *selectedGroup = (severalSelected ? this->selectedGroup() : 0); bool enabled = !isLocked() && severalSelected && (!selectedGroup || selectedGroup->isColumn()); if (!enabled) return; // Get the first selected note: we will group selected items just before: Note *first = firstSelected(); // if (selectedGroup != 0 || first == 0) // return; m_loaded = false; // Hack to avoid notes to be unselected and new notes to be selected: // Create and insert the receiving group: Note *group = new Note(this); if (first->isFree()) { insertNote(group, 0L, Note::BottomColumn, QPointF(first->x(), first->y()), /*animateNewPosition=*/false); } else { insertNote(group, first, Note::TopInsert, QPointF(), /*animateNewPosition=*/false); } // Put a FAKE UNSELECTED note in the new group, so if the new group is inside an allSelected() group, the parent group is not moved inside the new group! Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this); insertNote(fakeNote, group, Note::BottomColumn, QPointF(), /*animateNewPosition=*/false); // Group the notes: Note *nextNote; Note *note = firstNote(); while (note) { nextNote = note->next(); note->groupIn(group); note = nextNote; } m_loaded = true; // Part 2 / 2 of the workaround! // Do cleanup: unplugNote(fakeNote); delete fakeNote; unselectAll(); group->setSelectedRecursively(true); // Notes were unselected by unplugging relayoutNotes(true); save(); } void BasketScene::noteUngroup() { Note *group = selectedGroup(); if (group && !group->isColumn()) { ungroupNote(group); relayoutNotes(true); } save(); } void BasketScene::unplugSelection(NoteSelection *selection) { for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked()) { unplugNote(toUnplug->note); } } void BasketScene::insertSelection(NoteSelection *selection, Note *after) { for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked()) { if (toUnplug->note->isGroup()) { Note *group = new Note(this); insertNote(group, after, Note::BottomInsert, QPointF(), /*animateNewPosition=*/false); Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this); insertNote(fakeNote, group, Note::BottomColumn, QPointF(), /*animateNewPosition=*/false); insertSelection(toUnplug->firstChild, fakeNote); unplugNote(fakeNote); delete fakeNote; after = group; } else { Note *note = toUnplug->note; note->setPrev(0); note->setNext(0); insertNote(note, after, Note::BottomInsert, QPointF(), /*animateNewPosition=*/true); after = note; } } } void BasketScene::selectSelection(NoteSelection *selection) { for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked()) { if (toUnplug->note->isGroup()) selectSelection(toUnplug); else toUnplug->note->setSelected(true); } } void BasketScene::noteMoveOnTop() { // TODO: Get the group containing the selected notes and first move inside the group, then inside parent group, then in the basket // TODO: Move on top/bottom... of the column or basjet NoteSelection *selection = selectedNotes(); unplugSelection(selection); // Replug the notes: Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this); if (isColumnsLayout()) { if (firstNote()->firstChild()) insertNote(fakeNote, firstNote()->firstChild(), Note::TopInsert, QPointF(), /*animateNewPosition=*/false); else insertNote(fakeNote, firstNote(), Note::BottomColumn, QPointF(), /*animateNewPosition=*/false); } else { // TODO: Also allow to move notes on top of a group!!!!!!! insertNote(fakeNote, 0, Note::BottomInsert, QPointF(0, 0), /*animateNewPosition=*/false); } insertSelection(selection, fakeNote); unplugNote(fakeNote); delete fakeNote; selectSelection(selection); relayoutNotes(true); save(); } void BasketScene::noteMoveOnBottom() { // TODO: Duplicate code: void noteMoveOn(); // TODO: Get the group containing the selected notes and first move inside the group, then inside parent group, then in the basket // TODO: Move on top/bottom... of the column or basjet NoteSelection *selection = selectedNotes(); unplugSelection(selection); // Replug the notes: Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this); if (isColumnsLayout()) insertNote(fakeNote, firstNote(), Note::BottomColumn, QPointF(), /*animateNewPosition=*/false); else { // TODO: Also allow to move notes on top of a group!!!!!!! insertNote(fakeNote, 0, Note::BottomInsert, QPointF(0, 0), /*animateNewPosition=*/false); } insertSelection(selection, fakeNote); unplugNote(fakeNote); delete fakeNote; selectSelection(selection); relayoutNotes(true); save(); } void BasketScene::moveSelectionTo(Note *here, bool below /* = true*/) { NoteSelection *selection = selectedNotes(); unplugSelection(selection); // Replug the notes: Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this); // if (isColumnsLayout()) insertNote(fakeNote, here, (below ? Note::BottomInsert : Note::TopInsert), QPointF(), /*animateNewPosition=*/false); // else { // // TODO: Also allow to move notes on top of a group!!!!!!! // insertNote(fakeNote, 0, Note::BottomInsert, QPoint(0, 0), /*animateNewPosition=*/false); // } insertSelection(selection, fakeNote); unplugNote(fakeNote); delete fakeNote; selectSelection(selection); relayoutNotes(true); save(); } void BasketScene::noteMoveNoteUp() { // TODO: Move between columns, even if they are empty !!!!!!! // TODO: if first note of a group, move just above the group! And let that even if there is no note before that group!!! Note *first = firstSelected(); Note *previous = first->prevShownInStack(); if (previous) moveSelectionTo(previous, /*below=*/false); } void BasketScene::noteMoveNoteDown() { Note *first = lastSelected(); Note *next = first->nextShownInStack(); if (next) moveSelectionTo(next, /*below=*/true); } void BasketScene::wheelEvent(QGraphicsSceneWheelEvent *event) { // Q3ScrollView::wheelEvent(event); QGraphicsScene::wheelEvent(event); } void BasketScene::linkLookChanged() { Note *note = m_firstNote; while (note) { note->linkLookChanged(); note = note->next(); } relayoutNotes(true); } void BasketScene::slotCopyingDone2(KIO::Job *job, const QUrl & /*from*/, const QUrl &to) { if (job->error()) { DEBUG_WIN << "Copy finished, ERROR"; return; } Note *note = noteForFullPath(to.path()); DEBUG_WIN << "Copy finished, load note: " + to.path() + (note ? "" : " --- NO CORRESPONDING NOTE"); if (note != 0L) { note->content()->loadFromFile(/*lazyLoad=*/false); if (isEncrypted()) note->content()->saveToFile(); if (m_focusedNote == note) // When inserting a new note we ensure it visible ensureNoteVisible(note); // But after loading it has certainly grown and if it was } // on bottom of the basket it's not visible entirely anymore } Note *BasketScene::noteForFullPath(const QString &path) { Note *note = firstNote(); Note *found; while (note) { found = note->noteForFullPath(path); if (found) return found; note = note->next(); } return 0; } void BasketScene::deleteFiles() { m_watcher->stopScan(); Tools::deleteRecursively(fullPath()); } QList BasketScene::usedStates() { QList states; FOR_EACH_NOTE(note) note->usedStates(states); return states; } void BasketScene::listUsedTags(QList &list) { if (!isLoaded()) { load(); } FOR_EACH_NOTE(child) child->listUsedTags(list); } /** Unfocus the previously focused note (unless it was null) * and focus the new @param note (unless it is null) if hasFocus() * Update m_focusedNote to the new one */ void BasketScene::setFocusedNote(Note *note) // void BasketScene::changeFocusTo(Note *note) { // Don't focus an hidden note: if (note != 0L && !note->isShown()) return; // When clicking a group, this group gets focused. But only content-based notes should be focused: if (note && note->isGroup()) note = note->firstRealChild(); // The first time a note is focused, it becomes the start of the Shift selection: if (m_startOfShiftSelectionNote == 0) m_startOfShiftSelectionNote = note; // Unfocus the old focused note: if (m_focusedNote != 0L) m_focusedNote->setFocused(false); // Notify the new one to draw a focus rectangle... only if the basket is focused: if (hasFocus() && note != 0L) note->setFocused(true); // Save the new focused note: m_focusedNote = note; } /** If no shown note is currently focused, try to find a shown note and focus it * Also update m_focusedNote to the new one (or null if there isn't) */ void BasketScene::focusANote() { if (countFounds() == 0) { // No note to focus setFocusedNote(0L); // m_startOfShiftSelectionNote = 0; return; } if (m_focusedNote == 0L) { // No focused note yet : focus the first shown Note *toFocus = (isFreeLayout() ? noteOnHome() : firstNoteShownInStack()); setFocusedNote(toFocus); // m_startOfShiftSelectionNote = m_focusedNote; return; } // Search a visible note to focus if the focused one isn't shown : Note *toFocus = m_focusedNote; if (toFocus && !toFocus->isShown()) toFocus = toFocus->nextShownInStack(); if (!toFocus && m_focusedNote) toFocus = m_focusedNote->prevShownInStack(); setFocusedNote(toFocus); // m_startOfShiftSelectionNote = toFocus; } Note *BasketScene::firstNoteInStack() { if (!firstNote()) return 0; if (firstNote()->content()) return firstNote(); else return firstNote()->nextInStack(); } Note *BasketScene::lastNoteInStack() { Note *note = lastNote(); while (note) { if (note->content()) return note; Note *possibleNote = note->lastRealChild(); if (possibleNote && possibleNote->content()) return possibleNote; note = note->prev(); } return 0; } Note *BasketScene::firstNoteShownInStack() { Note *first = firstNoteInStack(); while (first && !first->isShown()) first = first->nextInStack(); return first; } Note *BasketScene::lastNoteShownInStack() { Note *last = lastNoteInStack(); while (last && !last->isShown()) last = last->prevInStack(); return last; } Note *BasketScene::noteOn(NoteOn side) { Note *bestNote = 0; int distance = -1; // int bestDistance = contentsWidth() * contentsHeight() * 10; int bestDistance = sceneRect().width() * sceneRect().height() * 10; Note *note = firstNoteShownInStack(); Note *primary = m_focusedNote->parentPrimaryNote(); while (note) { switch (side) { case LEFT_SIDE: distance = m_focusedNote->distanceOnLeftRight(note, LEFT_SIDE); break; case RIGHT_SIDE: distance = m_focusedNote->distanceOnLeftRight(note, RIGHT_SIDE); break; case TOP_SIDE: distance = m_focusedNote->distanceOnTopBottom(note, TOP_SIDE); break; case BOTTOM_SIDE: distance = m_focusedNote->distanceOnTopBottom(note, BOTTOM_SIDE); break; } if ((side == TOP_SIDE || side == BOTTOM_SIDE || primary != note->parentPrimaryNote()) && note != m_focusedNote && distance > 0 && distance < bestDistance) { bestNote = note; bestDistance = distance; } note = note->nextShownInStack(); } return bestNote; } Note *BasketScene::firstNoteInGroup() { Note *child = m_focusedNote; Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : 0); while (parent) { if (parent->firstChild() != child && !parent->isColumn()) return parent->firstRealChild(); child = parent; parent = parent->parentNote(); } return 0; } Note *BasketScene::noteOnHome() { // First try to find the first note of the group containing the focused note: Note *child = m_focusedNote; Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : 0); while (parent) { if (parent->nextShownInStack() != m_focusedNote) return parent->nextShownInStack(); child = parent; parent = parent->parentNote(); } // If it was not found, then focus the very first note in the basket: if (isFreeLayout()) { Note *first = firstNoteShownInStack(); // The effective first note found Note *note = first; // The current note, to compare with the previous first note, if this new note is more on top if (note) note = note->nextShownInStack(); while (note) { if (note->y() < first->y() || (note->y() == first->y() && note->x() < first->x())) first = note; note = note->nextShownInStack(); } return first; } else return firstNoteShownInStack(); } Note *BasketScene::noteOnEnd() { Note *child = m_focusedNote; Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : 0); Note *lastChild; while (parent) { lastChild = parent->lastRealChild(); if (lastChild && lastChild != m_focusedNote) { if (lastChild->isShown()) return lastChild; lastChild = lastChild->prevShownInStack(); if (lastChild && lastChild->isShown() && lastChild != m_focusedNote) return lastChild; } child = parent; parent = parent->parentNote(); } if (isFreeLayout()) { Note *last; Note *note; last = note = firstNoteShownInStack(); note = note->nextShownInStack(); while (note) { if (note->bottom() > last->bottom() || (note->bottom() == last->bottom() && note->x() > last->x())) last = note; note = note->nextShownInStack(); } return last; } else return lastNoteShownInStack(); } void BasketScene::keyPressEvent(QKeyEvent *event) { if (isDuringEdit()) { QGraphicsScene::keyPressEvent(event); /*if( event->key() == Qt::Key_Return ) { m_editor->graphicsWidget()->setFocus(); } else if( event->key() == Qt::Key_Escape) { closeEditor(); }*/ event->accept(); return; } if (event->key() == Qt::Key_Escape) { if (decoration()->filterData().isFiltering) decoration()->filterBar()->reset(); else unselectAll(); } if (countFounds() == 0) return; if (!m_focusedNote) return; Note *toFocus = 0L; switch (event->key()) { case Qt::Key_Down: toFocus = (isFreeLayout() ? noteOn(BOTTOM_SIDE) : m_focusedNote->nextShownInStack()); if (toFocus) break; // scrollBy(0, 30); // This cases do not move focus to another note... return; case Qt::Key_Up: toFocus = (isFreeLayout() ? noteOn(TOP_SIDE) : m_focusedNote->prevShownInStack()); if (toFocus) break; // scrollBy(0, -30); // This cases do not move focus to another note... return; case Qt::Key_PageDown: if (isFreeLayout()) { Note *lastFocused = m_focusedNote; for (int i = 0; i < 10 && m_focusedNote; ++i) m_focusedNote = noteOn(BOTTOM_SIDE); toFocus = m_focusedNote; m_focusedNote = lastFocused; } else { toFocus = m_focusedNote; for (int i = 0; i < 10 && toFocus; ++i) toFocus = toFocus->nextShownInStack(); } if (toFocus == 0L) toFocus = (isFreeLayout() ? noteOnEnd() : lastNoteShownInStack()); if (toFocus && toFocus != m_focusedNote) break; // scrollBy(0, visibleHeight() / 2); // This cases do not move focus to another note... // scrollBy(0, viewport()->height() / 2); // This cases do not move focus to another note... return; case Qt::Key_PageUp: if (isFreeLayout()) { Note *lastFocused = m_focusedNote; for (int i = 0; i < 10 && m_focusedNote; ++i) m_focusedNote = noteOn(TOP_SIDE); toFocus = m_focusedNote; m_focusedNote = lastFocused; } else { toFocus = m_focusedNote; for (int i = 0; i < 10 && toFocus; ++i) toFocus = toFocus->prevShownInStack(); } if (toFocus == 0L) toFocus = (isFreeLayout() ? noteOnHome() : firstNoteShownInStack()); if (toFocus && toFocus != m_focusedNote) break; // scrollBy(0, - visibleHeight() / 2); // This cases do not move focus to another note... // scrollBy(0, - viewport()->height() / 2); // This cases do not move focus to another note... return; case Qt::Key_Home: toFocus = noteOnHome(); break; case Qt::Key_End: toFocus = noteOnEnd(); break; case Qt::Key_Left: if (m_focusedNote->tryFoldParent()) return; if ((toFocus = noteOn(LEFT_SIDE))) break; if ((toFocus = firstNoteInGroup())) break; // scrollBy(-30, 0); // This cases do not move focus to another note... return; case Qt::Key_Right: if (m_focusedNote->tryExpandParent()) return; if ((toFocus = noteOn(RIGHT_SIDE))) break; // scrollBy(30, 0); // This cases do not move focus to another note... return; case Qt::Key_Space: // This case do not move focus to another note... if (m_focusedNote) { m_focusedNote->setSelected(!m_focusedNote->isSelected()); event->accept(); } else event->ignore(); return; // ... so we return after the process default: return; } if (toFocus == 0L) { // If no direction keys have been pressed OR reached out the begin or end event->ignore(); // Important !! return; } if (event->modifiers() & Qt::ShiftModifier) { // Shift+arrowKeys selection if (m_startOfShiftSelectionNote == 0L) m_startOfShiftSelectionNote = toFocus; ensureNoteVisible(toFocus); // Important: this line should be before the other ones because else repaint would be done on the wrong part! selectRange(m_startOfShiftSelectionNote, toFocus); setFocusedNote(toFocus); event->accept(); return; } else /*if (toFocus != m_focusedNote)*/ { // Move focus to ANOTHER note... ensureNoteVisible(toFocus); // Important: this line should be before the other ones because else repaint would be done on the wrong part! setFocusedNote(toFocus); m_startOfShiftSelectionNote = toFocus; if (!(event->modifiers() & Qt::ControlModifier)) // ... select only current note if Control unselectAllBut(m_focusedNote); event->accept(); return; } event->ignore(); // Important !! } /** Select a range of notes and deselect the others. * The order between start and end has no importance (end could be before start) */ void BasketScene::selectRange(Note *start, Note *end, bool unselectOthers /*= true*/) { Note *cur; Note *realEnd = 0L; // Avoid crash when start (or end) is null if (start == 0L) start = end; else if (end == 0L) end = start; // And if *both* are null if (start == 0L) { if (unselectOthers) unselectAll(); return; } // In case there is only one note to select if (start == end) { if (unselectOthers) unselectAllBut(start); else start->setSelected(true); return; } // Free layout baskets should select range as if we were drawing a rectangle between start and end: if (isFreeLayout()) { QRectF startRect(start->x(), start->y(), start->width(), start->height()); QRectF endRect(end->x(), end->y(), end->width(), end->height()); QRectF toSelect = startRect.united(endRect); selectNotesIn(toSelect, /*invertSelection=*/false, unselectOthers); return; } // Search the REAL first (and deselect the others before it) : for (cur = firstNoteInStack(); cur != 0L; cur = cur->nextInStack()) { if (cur == start || cur == end) break; if (unselectOthers) cur->setSelected(false); } // Select the notes after REAL start, until REAL end : if (cur == start) realEnd = end; else if (cur == end) realEnd = start; for (/*cur = cur*/; cur != 0L; cur = cur->nextInStack()) { cur->setSelected(cur->isShown()); // Select all notes in the range, but only if they are shown if (cur == realEnd) break; } if (!unselectOthers) return; // Deselect the remaining notes : if (cur) cur = cur->nextInStack(); for (/*cur = cur*/; cur != 0L; cur = cur->nextInStack()) cur->setSelected(false); } void BasketScene::focusInEvent(QFocusEvent *event) { // Focus cannot be get with Tab when locked, but a click can focus the basket! if (isLocked()) { if (m_button) { QGraphicsScene::focusInEvent(event); QTimer::singleShot(0, m_button, SLOT(setFocus())); } } else { QGraphicsScene::focusInEvent(event); focusANote(); // hasFocus() is true at this stage, note will be focused } } void BasketScene::focusOutEvent(QFocusEvent *) { if (m_focusedNote != 0L) m_focusedNote->setFocused(false); } void BasketScene::ensureNoteVisible(Note *note) { if (!note->isShown()) // Logical! return; if (note == editedNote()) // HACK: When filtering while editing big notes, etc... cause unwanted scrolls return; m_view->ensureVisible(note); /*// int bottom = note->y() + qMin(note->height(), visibleHeight()); // int finalRight = note->x() + qMin(note->width() + (note->hasResizer() ? Note::RESIZER_WIDTH : 0), visibleWidth()); qreal bottom = note->y() + qMin(note->height(), (qreal)m_view->viewport()->height()); qreal finalRight = note->x() + qMin(note->width() + (note->hasResizer() ? Note::RESIZER_WIDTH : 0), (qreal)m_view->viewport()->width()); m_view->ensureVisible(finalRight, bottom, 0, 0); m_view->ensureVisible(note->x(), note->y(), 0, 0);*/ } void BasketScene::addWatchedFile(const QString &fullPath) { // DEBUG_WIN << "Watcher>Add Monitoring Of : " + fullPath + ""; m_watcher->addFile(fullPath); } void BasketScene::removeWatchedFile(const QString &fullPath) { // DEBUG_WIN << "Watcher>Remove Monitoring Of : " + fullPath + ""; m_watcher->removeFile(fullPath); } void BasketScene::watchedFileModified(const QString &fullPath) { if (!m_modifiedFiles.contains(fullPath)) m_modifiedFiles.append(fullPath); // If a big file is saved by an application, notifications are send several times. // We wait they are not sent anymore to consider the file complete! m_watcherTimer.setSingleShot(true); m_watcherTimer.start(200); DEBUG_WIN << "Watcher>Modified : " + fullPath + ""; } void BasketScene::watchedFileDeleted(const QString &fullPath) { Note *note = noteForFullPath(fullPath); removeWatchedFile(fullPath); if (note) { NoteSelection *selection = selectedNotes(); unselectAllBut(note); noteDeleteWithoutConfirmation(); while (selection) { selection->note->setSelected(true); selection = selection->nextStacked(); } } DEBUG_WIN << "Watcher>Removed : " + fullPath + ""; } void BasketScene::updateModifiedNotes() { for (QList::iterator it = m_modifiedFiles.begin(); it != m_modifiedFiles.end(); ++it) { Note *note = noteForFullPath(*it); if (note) note->content()->loadFromFile(/*lazyLoad=*/false); } m_modifiedFiles.clear(); } bool BasketScene::setProtection(int type, QString key) { #ifdef HAVE_LIBGPGME if (type == PasswordEncryption || // Ask a new password m_encryptionType != type || m_encryptionKey != key) { int savedType = m_encryptionType; QString savedKey = m_encryptionKey; m_encryptionType = type; m_encryptionKey = key; m_gpg->clearCache(); if (saveAgain()) { emit propertiesChanged(this); } else { m_encryptionType = savedType; m_encryptionKey = savedKey; m_gpg->clearCache(); return false; } } return true; #else m_encryptionType = type; m_encryptionKey = key; return false; #endif } bool BasketScene::saveAgain() { bool result = false; m_watcher->stopScan(); // Re-encrypt basket file: result = save(); // Re-encrypt every note files recursively: if (result) { FOR_EACH_NOTE(note) { result = note->saveAgain(); if (!result) break; } } m_watcher->startScan(); return result; } bool BasketScene::loadFromFile(const QString &fullPath, QString *string) { QByteArray array; if (loadFromFile(fullPath, &array)) { *string = QString::fromUtf8(array.data(), array.size()); return true; } else return false; } bool BasketScene::isEncrypted() { return (m_encryptionType != NoEncryption); } bool BasketScene::isFileEncrypted() { QFile file(fullPath() + ".basket"); if (file.open(QIODevice::ReadOnly)) { // Should be ASCII anyways QString line = file.readLine(32); if (line.startsWith("-----BEGIN PGP MESSAGE-----")) return true; } return false; } bool BasketScene::loadFromFile(const QString &fullPath, QByteArray *array) { QFile file(fullPath); bool encrypted = false; if (file.open(QIODevice::ReadOnly)) { *array = file.readAll(); QByteArray magic = "-----BEGIN PGP MESSAGE-----"; int i = 0; if (array->size() > magic.size()) for (i = 0; array->at(i) == magic[i]; ++i) ; if (i == magic.size()) { encrypted = true; } file.close(); #ifdef HAVE_LIBGPGME if (encrypted) { QByteArray tmp(*array); tmp.detach(); // Only use gpg-agent for private key encryption since it doesn't // cache password used in symmetric encryption. m_gpg->setUseGnuPGAgent(Settings::useGnuPGAgent() && m_encryptionType == PrivateKeyEncryption); if (m_encryptionType == PrivateKeyEncryption) m_gpg->setText(i18n("Please enter the password for the following private key:"), false); else m_gpg->setText(i18n("Please enter the password for the basket %1:", basketName()), false); // Used when decrypting return m_gpg->decrypt(tmp, array); } #else if (encrypted) { return false; } #endif return true; } else return false; } bool BasketScene::saveToFile(const QString &fullPath, const QString &string) { QByteArray array = string.toUtf8(); return saveToFile(fullPath, array); } bool BasketScene::saveToFile(const QString &fullPath, const QByteArray &array) { ulong length = array.size(); bool success = true; QByteArray tmp; #ifdef HAVE_LIBGPGME if (isEncrypted()) { QString key = QString(); // We only use gpg-agent for private key encryption and saving without // public key doesn't need one. m_gpg->setUseGnuPGAgent(false); if (m_encryptionType == PrivateKeyEncryption) { key = m_encryptionKey; // public key doesn't need password m_gpg->setText("", false); } else m_gpg->setText(i18n("Please assign a password to the basket %1:", basketName()), true); // Used when defining a new password success = m_gpg->encrypt(array, length, &tmp, key); length = tmp.size(); } else tmp = array; #else success = !isEncrypted(); if (success) tmp = array; #endif /*if (success && (success = file.open(QIODevice::WriteOnly))){ success = (file.write(tmp) == (Q_LONG)tmp.size()); file.close(); }*/ if (success) return safelySaveToFile(fullPath, tmp, length); else return false; } /** * A safer version of saveToFile, that doesn't perform encryption. To save a * file owned by a basket (i.e. a basket or a note file), use saveToFile(), but * to save to another file, (e.g. the basket hierarchy), use this function * instead. */ /*static*/ bool BasketScene::safelySaveToFile(const QString &fullPath, const QByteArray &array, unsigned long length) { // Modulus operandi: // 1. Use QSaveFile to try and save the file // 2. Show a modal dialog (with the error) when bad things happen // 3. We keep trying (at increasing intervals, up until every minute) // until we finally save the file. // The error dialog is static to make sure we never show the dialog twice, static DiskErrorDialog *dialog = 0; static const uint maxDelay = 60 * 1000; // ms uint retryDelay = 1000; // ms bool success = false; do { QSaveFile saveFile(fullPath); if (saveFile.open(QIODevice::WriteOnly)) { saveFile.write(array, length); if (saveFile.commit()) success = true; } if (!success) { if (!dialog) { dialog = new DiskErrorDialog(i18n("Error while saving"), saveFile.errorString(), qApp->activeWindow()); } if (!dialog->isVisible()) dialog->show(); static const uint sleepDelay = 50; // ms for (uint i = 0; i < retryDelay / sleepDelay; ++i) { qApp->processEvents(); } // Double the retry delay, but don't go over the max. retryDelay = qMin(maxDelay, retryDelay * 2); // ms } } while (!success); if (dialog) dialog->deleteLater(); dialog = NULL; return true; // Guess we can't really return a fail } /*static*/ bool BasketScene::safelySaveToFile(const QString &fullPath, const QString &string) { QByteArray bytes = string.toUtf8(); return safelySaveToFile(fullPath, bytes, bytes.length()); } void BasketScene::lock() { #ifdef HAVE_LIBGPGME closeEditor(); m_gpg->clearCache(); m_locked = true; enableActions(); deleteNotes(); m_loaded = false; m_loadingLaunched = false; #endif } diff --git a/src/basketscene.h b/src/basketscene.h index a31c30e..d6e6bc9 100644 --- a/src/basketscene.h +++ b/src/basketscene.h @@ -1,749 +1,735 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef BASKET_H #define BASKET_H #include #include #include #include #include #include #include #include "config.h" #include "note.h" // For Note::Zone class QFrame; class QPixmap; class QPushButton; class QDomDocument; class QDomElement; class QContextMenuEvent; class QDragLeaveEvent; class QDragEnterEvent; class QDragMoveEvent; class QDropEvent; class QEvent; class QFocusEvent; class QHelpEvent; class QKeyEvent; class QMouseEvent; class QResizeEvent; class QWheelEvent; class QAction; class KDirWatch; class QKeySequence; class QUrl; namespace KIO { class Job; } class DecoratedBasket; class Note; class NoteEditor; class Tag; class TransparentWidget; #ifdef HAVE_LIBGPGME class KGpgMe; #endif /** * @author Sébastien Laoût */ class BasketScene : public QGraphicsScene { Q_OBJECT public: enum EncryptionTypes { NoEncryption = 0, PasswordEncryption = 1, PrivateKeyEncryption = 2 }; public: /// CONSTRUCTOR AND DESTRUCTOR: BasketScene(QWidget *parent, const QString &folderName); ~BasketScene() override; /// USER INTERACTION: private: bool m_noActionOnMouseRelease; bool m_ignoreCloseEditorOnNextMouseRelease; QPointF m_pressPos; bool m_canDrag; public: void drawBackground(QPainter *painter, const QRectF &rect) override; void drawForeground(QPainter *painter, const QRectF &rect) override; void enterEvent(QEvent *); void leaveEvent(QEvent *); void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; void clickedToInsert(QGraphicsSceneMouseEvent *event, Note *clicked = 0, int zone = 0); private slots: void setFocusIfNotInPopupMenu(); signals: void crossReference(QString link); /// LAYOUT: private: Note *m_firstNote; int m_columnsCount; bool m_mindMap; Note *m_resizingNote; int m_pickedResizer; Note *m_movingNote; QPoint m_pickedHandle; QSet m_notesToBeDeleted; public: qreal tmpWidth; qreal tmpHeight; public: void unsetNotesWidth(); void relayoutNotes(bool animate); Note *noteAt(QPointF pos); inline Note *firstNote() { return m_firstNote; } inline int columnsCount() { return m_columnsCount; } inline bool isColumnsLayout() { return m_columnsCount > 0; } inline bool isFreeLayout() { return m_columnsCount <= 0; } inline bool isMindMap() { return isFreeLayout() && m_mindMap; } Note *resizingNote() { return m_resizingNote; } void deleteNotes(); Note *lastNote(); void setDisposition(int disposition, int columnCount); void equalizeColumnSizes(); /// NOTES INSERTION AND REMOVAL: public: /// The following methods assume that the note(s) to insert already all have 'this' as the parent basket: void prependNoteIn(Note *note, Note *in); /// << Add @p note (and the next linked notes) as the first note(s) of the group @p in. void appendNoteIn(Note *note, Note *in); /// << Add @p note (and the next linked notes) as the last note(s) of the group @p in. void appendNoteAfter(Note *note, Note *after); /// << Add @p note (and the next linked notes) just after (just below) the note @p after. void appendNoteBefore(Note *note, Note *before); /// << Add @p note (and the next linked notes) just before (just above) the note @p before. void groupNoteAfter(Note *note, Note *with); /// << Add a group at @p with place, move @p with in it, and add @p note (and the next linked notes) just after the group. void groupNoteBefore(Note *note, Note *with); /// << Add a group at @p with place, move @p with in it, and add @p note (and the next linked notes) just before the group. void unplugNote(Note *note); /// << Unplug @p note (and its child notes) from the basket (and also decrease counts...). /// << After that, you should delete the notes yourself. Do not call prepend/append/group... functions two times: unplug and ok void ungroupNote(Note *group); /// << Unplug @p group but put child notes at its place. /// And this one do almost all the above methods depending on the context: void insertNote(Note *note, Note *clicked, int zone, const QPointF &pos = QPointF(), bool animateNewPosition = false); void insertCreatedNote(Note *note); /// And working with selections: void unplugSelection(NoteSelection *selection); void insertSelection(NoteSelection *selection, Note *after); void selectSelection(NoteSelection *selection); protected slots: void doCleanUp(); private: void preparePlug(Note *note); private: Note *m_clickedToInsert; int m_zoneToInsert; QPointF m_posToInsert; Note *m_savedClickedToInsert; int m_savedZoneToInsert; QPointF m_savedPosToInsert; bool m_isInsertPopupMenu; QAction *m_insertMenuTitle; public: void saveInsertionData(); void restoreInsertionData(); void resetInsertionData(); public slots: void insertEmptyNote(int type); void insertWizard(int type); void insertColor(const QColor &color); void insertImage(const QPixmap &image); void pasteNote(QClipboard::Mode mode = QClipboard::Clipboard); void delayedCancelInsertPopupMenu(); void setInsertPopupMenu() { m_isInsertPopupMenu = true; } void cancelInsertPopupMenu() { m_isInsertPopupMenu = false; } private slots: void hideInsertPopupMenu(); void timeoutHideInsertPopupMenu(); /// TOOL TIPS: protected: void helpEvent(QGraphicsSceneHelpEvent *event) override; /// ANIMATIONS: private: QTimeLine *m_animationTimeLine; static const int ANIMATION_DELAY; public slots: void animationFrameChanged(int); void animateLoad(); void finishAnimation(); /// LOAD AND SAVE: private: bool m_loaded; bool m_loadingLaunched; bool m_locked; bool m_shouldConvertPlainTextNotes; QFrame *m_decryptBox; QPushButton *m_button; int m_encryptionType; QString m_encryptionKey; #ifdef HAVE_LIBGPGME KGpgMe *m_gpg; #endif QTimer m_inactivityAutoLockTimer; QTimer m_commitdelay; void enableActions(); private slots: void loadNotes(const QDomElement ¬es, Note *parent); void saveNotes(QXmlStreamWriter &stream, Note *parent); void unlock(); protected slots: void inactivityAutoLockTimeout(); public slots: void load(); void loadProperties(const QDomElement &properties); void saveProperties(QXmlStreamWriter &stream); bool save(); void commitEdit(); void reload(); public: bool isEncrypted(); bool isFileEncrypted(); bool isLocked() { return m_locked; }; void lock(); bool isLoaded() { return m_loaded; }; bool loadingLaunched() { return m_loadingLaunched; }; bool loadFromFile(const QString &fullPath, QString *string); bool loadFromFile(const QString &fullPath, QByteArray *array); bool saveToFile(const QString &fullPath, const QString &string); bool saveToFile(const QString &fullPath, const QByteArray &array); //[Encrypt and] save binary content static bool safelySaveToFile(const QString &fullPath, const QByteArray &array, unsigned long length); static bool safelySaveToFile(const QString &fullPath, const QString &string); bool setProtection(int type, QString key); int encryptionType() { return m_encryptionType; }; QString encryptionKey() { return m_encryptionKey; }; bool saveAgain(); /// BACKGROUND: private: QColor m_backgroundColorSetting; QString m_backgroundImageName; QPixmap *m_backgroundPixmap; QPixmap *m_opaqueBackgroundPixmap; QPixmap *m_selectedBackgroundPixmap; bool m_backgroundTiled; QColor m_textColorSetting; public: inline bool hasBackgroundImage() { return m_backgroundPixmap != 0; } inline const QPixmap *backgroundPixmap() { return m_backgroundPixmap; } inline bool isTiledBackground() { return m_backgroundTiled; } inline QString backgroundImageName() { return m_backgroundImageName; } inline QColor backgroundColorSetting() { return m_backgroundColorSetting; } inline QColor textColorSetting() { return m_textColorSetting; } QColor backgroundColor() const; QColor textColor() const; void setAppearance(const QString &icon, const QString &name, const QString &backgroundImage, const QColor &backgroundColor, const QColor &textColor); void blendBackground(QPainter &painter, const QRectF &rect, qreal xPainter = -1, qreal yPainter = -1, bool opaque = false, QPixmap *bg = 0); void blendBackground(QPainter &painter, const QRectF &rect, bool opaque, QPixmap *bg); void unbufferizeAll(); void subscribeBackgroundImages(); void unsubscribeBackgroundImages(); /// KEYBOARD SHORTCUT: public: QAction *m_action; private: int m_shortcutAction; private slots: void activatedShortcut(); public: QKeySequence shortcut() { return m_action->shortcut(); } int shortcutAction() { return m_shortcutAction; } void setShortcut(QKeySequence shortcut, int action); /// USER INTERACTION: private: Note *m_hoveredNote; int m_hoveredZone; bool m_lockedHovering; bool m_underMouse; QRectF m_inserterRect; bool m_inserterShown; bool m_inserterSplit; bool m_inserterTop; bool m_inserterGroup; void placeInserter(Note *note, int zone); void removeInserter(); public: // bool inserterShown() { return m_inserterShown; } bool inserterSplit() { return m_inserterSplit; } bool inserterGroup() { return m_inserterGroup; } public slots: void doHoverEffects(Note *note, Note::Zone zone, const QPointF &pos = QPointF(0, 0)); /// << @p pos is optional and only used to show the link target in the statusbar void doHoverEffects(const QPointF &pos); void doHoverEffects(); // The same, but using the current cursor position void mouseEnteredEditorWidget(); public: void popupTagsMenu(Note *note); void popupEmblemMenu(Note *note, int emblemNumber); void addTagToSelectedNotes(Tag *tag); void removeTagFromSelectedNotes(Tag *tag); void removeAllTagsFromSelectedNotes(); void addStateToSelectedNotes(State *state); void changeStateOfSelectedNotes(State *state); bool selectedNotesHaveTags(); const QRectF &inserterRect() { return m_inserterRect; } bool inserterShown() { return m_inserterShown; } void drawInserter(QPainter &painter, qreal xPainter, qreal yPainter); DecoratedBasket *decoration(); State *stateForTagFromSelectedNotes(Tag *tag); public slots: void activatedTagShortcut(Tag *tag); void recomputeAllStyles(); void removedStates(const QList &deletedStates); private slots: void toggledTagInMenu(QAction *act); void toggledStateInMenu(QAction *act); void unlockHovering(); void disableNextClick(); public: Note *m_tagPopupNote; private: Tag *m_tagPopup; QTime m_lastDisableClick; /// SELECTION: private: bool m_isSelecting; bool m_selectionStarted; bool m_selectionInvert; QPointF m_selectionBeginPoint; QPointF m_selectionEndPoint; QRectF m_selectionRect; QTimer m_autoScrollSelectionTimer; void stopAutoScrollSelection(); private slots: void doAutoScrollSelection(); public: inline bool isSelecting() { return m_isSelecting; } inline const QRectF &selectionRect() { return m_selectionRect; } void selectNotesIn(const QRectF &rect, bool invertSelection, bool unselectOthers = true); void resetWasInLastSelectionRect(); void selectAll(); void unselectAll(); void invertSelection(); void unselectAllBut(Note *toSelect); void invertSelectionOf(Note *toSelect); QColor selectionRectInsideColor(); Note *theSelectedNote(); NoteSelection *selectedNotes(); /// BLANK SPACES DRAWING: private: QList m_blankAreas; void recomputeBlankRects(); QWidget *m_cornerWidget; /// COMMUNICATION WITH ITS CONTAINER: signals: void postMessage(const QString &message); /// << Post a temporary message in the statusBar. void setStatusBarText(const QString &message); /// << Set the permanent statusBar text or reset it if message isEmpty(). void resetStatusBarText(); /// << Equivalent to setStatusBarText(""). void propertiesChanged(BasketScene *basket); void countsChanged(BasketScene *basket); public slots: void linkLookChanged(); void signalCountsChanged(); private: QTimer m_timerCountsChanged; private slots: void countsChangedTimeOut(); /// NOTES COUNTING: public: void addSelectedNote() { ++m_countSelecteds; signalCountsChanged(); } void removeSelectedNote() { --m_countSelecteds; signalCountsChanged(); } void resetSelectedNote() { m_countSelecteds = 0; signalCountsChanged(); } // FIXME: Useful ??? int count() { return m_count; } int countFounds() { return m_countFounds; } int countSelecteds() { return m_countSelecteds; } private: int m_count; int m_countFounds; int m_countSelecteds; /// PROPERTIES: public: QString basketName() { return m_basketName; } QString icon() { return m_icon; } QString folderName() { return m_folderName; } QString fullPath(); QString fullPathForFileName(const QString &fileName); // Full path of an [existing or not] note in this basket static QString fullPathForFolderName(const QString &folderName); private: QString m_basketName; QString m_icon; QString m_folderName; /// ACTIONS ON SELECTED NOTES FROM THE INTERFACE: public slots: void noteEdit(Note *note = 0L, bool justAdded = false, const QPointF &clickedPoint = QPointF()); void showEditedNoteWhileFiltering(); void noteDelete(); void noteDeleteWithoutConfirmation(bool deleteFilesToo = true); void noteCopy(); void noteCut(); void noteOpen(Note *note = 0L); void noteOpenWith(Note *note = 0L); void noteSaveAs(); void noteGroup(); void noteUngroup(); void noteMoveOnTop(); void noteMoveOnBottom(); void noteMoveNoteUp(); void noteMoveNoteDown(); void moveSelectionTo(Note *here, bool below); public: enum CopyMode { CopyToClipboard, CopyToSelection, CutToClipboard }; void doCopy(CopyMode copyMode); bool selectionIsOneGroup(); Note *selectedGroup(); Note *firstSelected(); Note *lastSelected(); /// NOTES EDITION: private: NoteEditor *m_editor; // QWidget *m_rightEditorBorder; TransparentWidget *m_leftEditorBorder; TransparentWidget *m_rightEditorBorder; bool m_redirectEditActions; bool m_editorTrackMouseEvent; qreal m_editorWidth; qreal m_editorHeight; QTimer m_inactivityAutoSaveTimer; bool m_doNotCloseEditor; QTextCursor m_textCursor; public: bool isDuringEdit() { return m_editor; } bool redirectEditActions() { return m_redirectEditActions; } bool hasTextInEditor(); bool hasSelectedTextInEditor(); bool selectedAllTextInEditor(); Note *editedNote(); protected slots: void selectionChangedInEditor(); void contentChangedInEditor(); void inactivityAutoSaveTimeout(); public slots: void editorCursorPositionChanged(); private: qreal m_editorX; qreal m_editorY; public slots: void placeEditor(bool andEnsureVisible = false); void placeEditorAndEnsureVisible(); bool closeEditor(bool deleteEmptyNote = true); void closeEditorDelayed(); void updateEditorAppearance(); void editorPropertiesChanged(); void openBasket(); void closeBasket(); /// FILTERING: public slots: void newFilter(const FilterData &data, bool andEnsureVisible = true); void filterAgain(bool andEnsureVisible = true); void filterAgainDelayed(); bool isFiltering(); /// DRAG AND DROP: private: bool m_isDuringDrag; QList m_draggedNotes; public: static void acceptDropEvent(QGraphicsSceneDragDropEvent *event, bool preCond = true); void dropEvent(QGraphicsSceneDragDropEvent *event) override; void blindDrop(QGraphicsSceneDragDropEvent *event); void blindDrop(const QMimeData *mimeData, Qt::DropAction dropAction, QObject *source); bool isDuringDrag() { return m_isDuringDrag; } QList draggedNotes() { return m_draggedNotes; } protected: void dragEnterEvent(QGraphicsSceneDragDropEvent *) override; void dragMoveEvent(QGraphicsSceneDragDropEvent *event) override; void dragLeaveEvent(QGraphicsSceneDragDropEvent *) override; public slots: void slotCopyingDone2(KIO::Job *job, const QUrl &from, const QUrl &to); public: Note *noteForFullPath(const QString &path); /// EXPORTATION: public: QList usedStates(); public: void listUsedTags(QList &list); /// MANAGE FOCUS: private: Note *m_focusedNote; public: void setFocusedNote(Note *note); void focusANote(); void focusANonSelectedNoteAbove(bool inSameColumn); void focusANonSelectedNoteBelow(bool inSameColumn); void focusANonSelectedNoteBelowOrThenAbove(); void focusANonSelectedNoteAboveOrThenBelow(); Note *focusedNote() { return m_focusedNote; } Note *firstNoteInStack(); Note *lastNoteInStack(); Note *firstNoteShownInStack(); Note *lastNoteShownInStack(); void selectRange(Note *start, Note *end, bool unselectOthers = true); /// FIXME: Not really a focus related method! void ensureNoteVisible(Note *note); void keyPressEvent(QKeyEvent *event) override; void focusInEvent(QFocusEvent *) override; void focusOutEvent(QFocusEvent *) override; QRectF noteVisibleRect(Note *note); // clipped global (desktop as origin) rectangle Note *firstNoteInGroup(); Note *noteOnHome(); Note *noteOnEnd(); enum NoteOn { LEFT_SIDE = 1, RIGHT_SIDE, TOP_SIDE, BOTTOM_SIDE }; Note *noteOn(NoteOn side); /// REIMPLEMENTED: public: void deleteFiles(); bool convertTexts(); public: void wheelEvent(QGraphicsSceneWheelEvent *event) override; public: Note *m_startOfShiftSelectionNote; /// THE NEW FILE WATCHER: private: KDirWatch *m_watcher; QTimer m_watcherTimer; QList m_modifiedFiles; public: void addWatchedFile(const QString &fullPath); void removeWatchedFile(const QString &fullPath); private slots: void watchedFileModified(const QString &fullPath); void watchedFileDeleted(const QString &fullPath); void updateModifiedNotes(); /// FROM OLD ARCHITECTURE ********************** public slots: void showFrameInsertTo() { } void resetInsertTo() { } void computeInsertPlace(const QPointF & /*cursorPosition*/) { } public: friend class SystemTray; /// SPEED OPTIMIZATION private: bool m_finishLoadOnFirstShow; bool m_relayoutOnNextShow; public: void aboutToBeActivated(); QGraphicsView *graphicsView() { return m_view; } private: QGraphicsView *m_view; }; #endif // BASKET_H diff --git a/src/basketstatusbar.cpp b/src/basketstatusbar.cpp index ce067af..57590a3 100644 --- a/src/basketstatusbar.cpp +++ b/src/basketstatusbar.cpp @@ -1,201 +1,187 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "basketstatusbar.h" #include #include #include #include #include #include #include #include #include #include "basketscene.h" #include "bnpview.h" #include "global.h" #include "tools.h" BasketStatusBar::BasketStatusBar(QStatusBar *bar) : m_bar(bar) , m_extension(0) , m_selectionStatus(0) , m_lockStatus(0) , m_basketStatus(0) , m_savedStatus(0) { } BasketStatusBar::BasketStatusBar(KParts::StatusBarExtension *extension) : m_bar(0) , m_extension(extension) , m_selectionStatus(0) , m_lockStatus(0) , m_basketStatus(0) , m_savedStatus(0) { } BasketStatusBar::~BasketStatusBar() { // delete m_extension; } QStatusBar *BasketStatusBar::statusBar() const { if (m_extension) return m_extension->statusBar(); else return m_bar; } void BasketStatusBar::addWidget(QWidget *widget, int stretch, bool permanent) { if (m_extension) m_extension->addStatusBarItem(widget, stretch, permanent); else if (permanent) m_bar->addPermanentWidget(widget, stretch); else m_bar->addWidget(widget, stretch); } void BasketStatusBar::setupStatusBar() { QWidget *parent = statusBar(); QObjectList lst = parent->findChildren("KRSqueezedTextLabel"); // Tools::printChildren(parent); if (lst.count() == 0) { m_basketStatus = new QLabel(parent); QSizePolicy policy(QSizePolicy::Ignored, QSizePolicy::Ignored); policy.setHorizontalStretch(0); policy.setVerticalStretch(0); policy.setHeightForWidth(false); m_basketStatus->setSizePolicy(policy); addWidget(m_basketStatus, 1, false); // Fit all extra space and is hiddable } else m_basketStatus = static_cast(lst.at(0)); lst.clear(); m_selectionStatus = new QLabel(i18n("Loading..."), parent); addWidget(m_selectionStatus, 0, true); m_lockStatus = new QLabel(0 /*this*/); m_lockStatus->setMinimumSize(18, 18); m_lockStatus->setAlignment(Qt::AlignCenter); // addWidget( m_lockStatus, 0, true ); m_lockStatus->installEventFilter(this); m_savedStatusPixmap = SmallIcon("document-save"); m_savedStatus = new QLabel(parent); m_savedStatus->setPixmap(m_savedStatusPixmap); m_savedStatus->setFixedSize(m_savedStatus->sizeHint()); m_savedStatus->clear(); // m_savedStatus->setPixmap(m_savedStatusIconSet.pixmap(QIconSet::Small, QIconSet::Disabled)); // m_savedStatus->setEnabled(false); addWidget(m_savedStatus, 0, true); m_savedStatus->setToolTip("

" + i18n("Shows if there are changes that have not yet been saved.")); } void BasketStatusBar::postStatusbarMessage(const QString &text) { if (statusBar()) statusBar()->showMessage(text, 2000); } void BasketStatusBar::setStatusText(const QString &txt) { if (m_basketStatus && m_basketStatus->text() != txt) m_basketStatus->setText(txt); } void BasketStatusBar::setStatusBarHint(const QString &hint) { if (hint.isEmpty()) updateStatusBarHint(); else setStatusText(hint); } void BasketStatusBar::updateStatusBarHint() { QString message = ""; if (Global::bnpView->currentBasket()->isDuringDrag()) message = i18n("Ctrl+drop: copy, Shift+drop: move, Shift+Ctrl+drop: link."); // Too much noise information: // else if (currentBasket()->inserterShown() && currentBasket()->inserterSplit() && !currentBasket()->inserterGroup()) // message = i18n("Click to insert a note, right click for more options. Click on the right of the line to group instead of insert."); // else if (currentBasket()->inserterShown() && currentBasket()->inserterSplit() && currentBasket()->inserterGroup()) // message = i18n("Click to group a note, right click for more options. Click on the left of the line to group instead of insert."); else if (Global::debugWindow) message = "DEBUG: " + Global::bnpView->currentBasket()->folderName(); setStatusText(message); } void BasketStatusBar::setLockStatus(bool isLocked) { if (!m_lockStatus) return; if (isLocked) { m_lockStatus->setPixmap(SmallIcon("encrypted.png")); m_lockStatus->setToolTip(i18n("

This basket is locked.
Click to unlock it.

").replace(QChar(' '), " ")); } else { m_lockStatus->clear(); m_lockStatus->setToolTip(i18n("

This basket is unlocked.
Click to lock it.

").replace(QChar(' '), " ")); } } void BasketStatusBar::setSelectionStatus(const QString &s) { if (m_selectionStatus) m_selectionStatus->setText(s); } void BasketStatusBar::setUnsavedStatus(bool isUnsaved) { if (!m_savedStatus) return; if (isUnsaved) { if (m_savedStatus->pixmap() == 0) m_savedStatus->setPixmap(m_savedStatusPixmap); } else m_savedStatus->clear(); } bool BasketStatusBar::eventFilter(QObject *obj, QEvent *event) { if (obj == m_lockStatus && event->type() == QEvent::MouseButtonPress) { QMouseEvent *mevent = dynamic_cast(event); if (mevent->button() & Qt::LeftButton) { Global::bnpView->lockBasket(); return true; } else { return QObject::eventFilter(obj, event); // standard event processing } } return QObject::eventFilter(obj, event); // standard event processing } diff --git a/src/basketstatusbar.h b/src/basketstatusbar.h index 17c992e..123a079 100644 --- a/src/basketstatusbar.h +++ b/src/basketstatusbar.h @@ -1,73 +1,59 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef BASKETSTATUSBAR_H #define BASKETSTATUSBAR_H #include #include #include "basket_export.h" class QStatusBar; namespace KParts { class StatusBarExtension; } class QWidget; class QLabel; /** @author Sébastien Laoût */ class BASKET_EXPORT BasketStatusBar : public QObject { Q_OBJECT public: explicit BasketStatusBar(QStatusBar *bar); BasketStatusBar(KParts::StatusBarExtension *extension); ~BasketStatusBar() override; public slots: /** GUI Main Window actions **/ void setStatusBarHint(const QString &hint); /// << Set a specific message or update if hint is empty void updateStatusBarHint(); /// << Display the current state message (dragging, editing) or reset the startsbar message void postStatusbarMessage(const QString &text); void setSelectionStatus(const QString &s); void setLockStatus(bool isLocked); void setupStatusBar(); void setUnsavedStatus(bool isUnsaved); protected: QStatusBar *statusBar() const; void addWidget(QWidget *widget, int stretch = 0, bool permanent = false); void setStatusText(const QString &txt); bool eventFilter(QObject *obj, QEvent *event) override; private: QStatusBar *m_bar; KParts::StatusBarExtension *m_extension; QLabel *m_selectionStatus; QLabel *m_lockStatus; QLabel *m_basketStatus; QLabel *m_savedStatus; QPixmap m_savedStatusPixmap; }; #endif diff --git a/src/basketview.cpp b/src/basketview.cpp index 1e3bbbf..9d1a97e 100644 --- a/src/basketview.cpp +++ b/src/basketview.cpp @@ -1,36 +1,22 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "basketview.h" #include "basketscene.h" BasketView::BasketView(BasketScene *scene, QWidget *parent) : QGraphicsView(scene, parent) { } BasketView::~BasketView() { } void BasketView::resizeEvent(QResizeEvent *) { static_cast(scene())->relayoutNotes(true); } diff --git a/src/basketview.h b/src/basketview.h index dd60a3a..07ce24a 100644 --- a/src/basketview.h +++ b/src/basketview.h @@ -1,38 +1,24 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef BASKET_VIEW_H #define BASKET_VIEW_H #include class BasketScene; class BasketView : public QGraphicsView { Q_OBJECT public: BasketView(BasketScene *scene, QWidget *parent = nullptr); ~BasketView() override; protected: void resizeEvent(QResizeEvent *event) override; }; #endif // BASKET_VIEW_H diff --git a/src/bnpview.cpp b/src/bnpview.cpp index 2ca695b..9c1b63e 100644 --- a/src/bnpview.cpp +++ b/src/bnpview.cpp @@ -1,3032 +1,3018 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "bnpview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef BASKET_USE_DRKONQI #include #endif // BASKET_USE_DRKONQI #include #include // usleep #include "archive.h" #include "backgroundmanager.h" #include "backup.h" #include "basketfactory.h" #include "basketlistview.h" #include "basketproperties.h" #include "basketscene.h" #include "basketstatusbar.h" #include "colorpicker.h" #include "crashhandler.h" #include "debugwindow.h" #include "decoratedbasket.h" #include "formatimporter.h" #include "gitwrapper.h" #include "history.h" #include "htmlexporter.h" #include "icon_names.h" #include "likeback.h" #include "newbasketdialog.h" #include "notedrag.h" #include "noteedit.h" // To launch InlineEditors::initToolBars() #include "notefactory.h" #include "password.h" #include "regiongrabber.h" #include "settings.h" #include "softwareimporters.h" #include "tools.h" #include "xmlwork.h" #include #include #include #include //#include "bnpviewadaptor.h" /** class BNPView: */ const int BNPView::c_delayTooltipTime = 275; BNPView::BNPView(QWidget *parent, const char *name, KXMLGUIClient *aGUIClient, KActionCollection *actionCollection, BasketStatusBar *bar) : QSplitter(Qt::Horizontal, parent) , m_actLockBasket(0) , m_actPassBasket(0) , m_loading(true) , m_newBasketPopup(false) , m_firstShow(true) , m_regionGrabber(0) , m_passiveDroppedSelection(0) , m_actionCollection(actionCollection) , m_guiClient(aGUIClient) , m_statusbar(bar) , m_tryHideTimer(0) , m_hideTimer(0) { // new BNPViewAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.registerObject("/BNPView", this); setObjectName(name); /* Settings */ Settings::loadConfig(); Global::bnpView = this; // Needed when loading the baskets: Global::backgroundManager = new BackgroundManager(); setupGlobalShortcuts(); m_history = new QUndoStack(this); initialize(); QTimer::singleShot(0, this, SLOT(lateInit())); } BNPView::~BNPView() { int treeWidth = Global::bnpView->sizes()[Settings::treeOnLeft() ? 0 : 1]; Settings::setBasketTreeWidth(treeWidth); if (currentBasket() && currentBasket()->isDuringEdit()) currentBasket()->closeEditor(); Settings::saveConfig(); Global::bnpView = 0; delete Global::systemTray; Global::systemTray = 0; delete m_colorPicker; delete m_statusbar; delete m_history; m_history = 0; NoteDrag::createAndEmptyCuttingTmpFolder(); // Clean the temporary folder we used } void BNPView::lateInit() { /* InlineEditors* instance = InlineEditors::instance(); if(instance) { KToolBar* toolbar = instance->richTextToolBar(); if(toolbar) toolbar->hide(); } */ #if 0 // This is the logic to show or hide Basket when it is started up; ideally, // it will take on its last state when KDE's session restore kicks in. if (!isPart()) { if (Settings::useSystray() && KCmdLineArgs::parsedArgs() && KCmdLineArgs::parsedArgs()->isSet("start-hidden")) { if (Global::mainWindow()) Global::mainWindow()->hide(); } else if (Settings::useSystray() && qApp->isSessionRestored()) { if (Global::mainWindow()) Global::mainWindow()->setShown(!Settings::startDocked()); } } #else #pragma message("Proper fix for the systray problem") #endif // If the main window is hidden when session is saved, Container::queryClose() // isn't called and the last value would be kept Settings::setStartDocked(true); Settings::saveConfig(); /* System tray icon */ Global::systemTray = new SystemTray(Global::activeMainWindow()); Global::systemTray->setIconByName(":/images/22-apps-basket"); connect(Global::systemTray, SIGNAL(showPart()), this, SIGNAL(showPart())); /*if (Settings::useSystray()) Global::systemTray->show();*/ // Load baskets DEBUG_WIN << "Baskets are loaded from " + Global::basketsFolder(); NoteDrag::createAndEmptyCuttingTmpFolder(); // If last exec hasn't done it: clean the temporary folder we will use Tag::loadTags(); // Tags should be ready before loading baskets, but tags need the mainContainer to be ready to create KActions! load(); // If no basket has been found, try to import from an older version, if (topLevelItemCount() <= 0) { QDir dir; dir.mkdir(Global::basketsFolder()); if (FormatImporter::shouldImportBaskets()) { FormatImporter::importBaskets(); load(); } if (topLevelItemCount() <= 0) { // Create first basket: BasketFactory::newBasket(/*icon=*/"", /*name=*/i18n("General"), /*backgroundImage=*/"", /*backgroundColor=*/QColor(), /*textColor=*/QColor(), /*templateName=*/"1column", /*createIn=*/0); GitWrapper::commitBasket(currentBasket()); GitWrapper::commitTagsXml(); } } // Load the Welcome Baskets if it is the First Time: if (!Settings::welcomeBasketsAdded()) { addWelcomeBaskets(); Settings::setWelcomeBasketsAdded(true); Settings::saveConfig(); } m_tryHideTimer = new QTimer(this); m_hideTimer = new QTimer(this); connect(m_tryHideTimer, SIGNAL(timeout()), this, SLOT(timeoutTryHide())); connect(m_hideTimer, SIGNAL(timeout()), this, SLOT(timeoutHide())); // Preload every baskets for instant filtering: /*StopWatch::start(100); QListViewItemIterator it(m_tree); while (it.current()) { BasketListViewItem *item = ((BasketListViewItem*)it.current()); item->basket()->load(); qApp->processEvents(); ++it; } StopWatch::check(100);*/ } void BNPView::addWelcomeBaskets() { // Possible paths where to find the welcome basket archive, trying the translated one, and falling back to the English one: QStringList possiblePaths; if (QString(Tools::systemCodeset()) == QString("UTF-8")) { // Welcome baskets are encoded in UTF-8. If the system is not, then use the English version: QString lang = QLocale().languageToString(QLocale().language()); possiblePaths.append(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "basket/welcome/Welcome_" + lang + ".baskets")); possiblePaths.append(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "basket/welcome/Welcome_" + lang.split('_')[0] + ".baskets")); } possiblePaths.append(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "basket/welcome/Welcome_en_US.baskets")); // Take the first EXISTING basket archive found: QDir dir; QString path; for (QStringList::Iterator it = possiblePaths.begin(); it != possiblePaths.end(); ++it) { if (dir.exists(*it)) { path = *it; break; } } // Extract: if (!path.isEmpty()) Archive::open(path); } void BNPView::onFirstShow() { // Don't enable LikeBack until bnpview is shown. This way it works better with kontact. /* LikeBack */ /* Global::likeBack = new LikeBack(LikeBack::AllButtons, / *showBarByDefault=* /true, Global::config()); Global::likeBack->setServer("basket.linux62.org", "/likeback/send.php"); Global:likeBack->setAcceptedLanguages(QStringList::split(";", "en;fr"), i18n("Only english and french languages are accepted.")); if (isPart()) Global::likeBack->disableBar(); // See BNPView::shown() and BNPView::hide(). */ if (isPart()) Global::likeBack->disableBar(); // See BNPView::shown() and BNPView::hide(). /* LikeBack::init(Global::config(), Global::about(), LikeBack::AllButtons); LikeBack::setServer("basket.linux62.org", "/likeback/send.php"); // LikeBack::setServer("localhost", "/~seb/basket/likeback/send.php"); LikeBack::setCustomLanguageMessage(i18n("Only english and french languages are accepted.")); // LikeBack::setWindowNamesListing(LikeBack:: / *NoListing* / / *WarnUnnamedWindows* / AllWindows); */ // In late init, because we need qApp->mainWidget() to be set! if (!isPart()) connectTagsMenu(); m_statusbar->setupStatusBar(); int treeWidth = Settings::basketTreeWidth(); if (treeWidth < 0) treeWidth = m_tree->fontMetrics().maxWidth() * 11; QList splitterSizes; splitterSizes.append(treeWidth); setSizes(splitterSizes); } void BNPView::setupGlobalShortcuts() { KActionCollection *ac = new KActionCollection(this); QAction *a = NULL; // Ctrl+Shift+W only works when started standalone: QWidget *basketMainWindow = qobject_cast(Global::bnpView->parent()); int modifier = Qt::CTRL + Qt::ALT + Qt::SHIFT; if (basketMainWindow) { a = ac->addAction("global_show_hide_main_window", Global::systemTray, SLOT(toggleActive())); a->setText(i18n("Show/hide main window")); a->setStatusTip( i18n("Allows you to show main Window if it is hidden, and to hide " "it if it is shown.")); KGlobalAccel::self()->setGlobalShortcut(a, (QKeySequence(modifier + Qt::Key_W))); } a = ac->addAction("global_paste", Global::bnpView, SLOT(globalPasteInCurrentBasket())); a->setText(i18n("Paste clipboard contents in current basket")); a->setStatusTip( i18n("Allows you to paste clipboard contents in the current basket " "without having to open the main window.")); KGlobalAccel::self()->setGlobalShortcut(a, QKeySequence(modifier + Qt::Key_V)); a = ac->addAction("global_show_current_basket", Global::bnpView, SLOT(showPassiveContentForced())); a->setText(i18n("Show current basket name")); a->setStatusTip( i18n("Allows you to know basket is current without opening " "the main window.")); a = ac->addAction("global_paste_selection", Global::bnpView, SLOT(pasteSelInCurrentBasket())); a->setText(i18n("Paste selection in current basket")); a->setStatusTip( i18n("Allows you to paste clipboard selection in the current basket " "without having to open the main window.")); KGlobalAccel::self()->setGlobalShortcut(a, (QKeySequence(Qt::CTRL + Qt::ALT + Qt::SHIFT + Qt::Key_S))); a = ac->addAction("global_new_basket", Global::bnpView, SLOT(askNewBasket())); a->setText(i18n("Create a new basket")); a->setStatusTip( i18n("Allows you to create a new basket without having to open the " "main window (you then can use the other global shortcuts to add " "a note, paste clipboard or paste selection in this new basket).")); a = ac->addAction("global_previous_basket", Global::bnpView, SLOT(goToPreviousBasket())); a->setText(i18n("Go to previous basket")); a->setStatusTip( i18n("Allows you to change current basket to the previous one without " "having to open the main window.")); a = ac->addAction("global_next_basket", Global::bnpView, SLOT(goToNextBasket())); a->setText(i18n("Go to next basket")); a->setStatusTip( i18n("Allows you to change current basket to the next one " "without having to open the main window.")); a = ac->addAction("global_note_add_html", Global::bnpView, SLOT(addNoteHtml())); a->setText(i18n("Insert text note")); a->setStatusTip( i18n("Add a text note to the current basket without having to open " "the main window.")); KGlobalAccel::self()->setGlobalShortcut(a, (QKeySequence(modifier + Qt::Key_T))); a = ac->addAction("global_note_add_image", Global::bnpView, SLOT(addNoteImage())); a->setText(i18n("Insert image note")); a->setStatusTip( i18n("Add an image note to the current basket without having to open " "the main window.")); a = ac->addAction("global_note_add_link", Global::bnpView, SLOT(addNoteLink())); a->setText(i18n("Insert link note")); a->setStatusTip( i18n("Add a link note to the current basket without having " "to open the main window.")); a = ac->addAction("global_note_add_color", Global::bnpView, SLOT(addNoteColor())); a->setText(i18n("Insert color note")); a->setStatusTip( i18n("Add a color note to the current basket without having to open " "the main window.")); a = ac->addAction("global_note_pick_color", Global::bnpView, SLOT(slotColorFromScreenGlobal())); a->setText(i18n("Pick color from screen")); a->setStatusTip( i18n("Add a color note picked from one pixel on screen to the current " "basket without " "having to open the main window.")); a = ac->addAction("global_note_grab_screenshot", Global::bnpView, SLOT(grabScreenshotGlobal())); a->setText(i18n("Grab screen zone")); a->setStatusTip( i18n("Grab a screen zone as an image in the current basket without " "having to open the main window.")); #if 0 a = ac->addAction("global_note_add_text", Global::bnpView, SLOT(addNoteText())); a->setText(i18n("Insert plain text note")); a->setStatusTip( i18n("Add a plain text note to the current basket without having to " "open the main window.")); #endif } void BNPView::initialize() { /// Configure the List View Columns: m_tree = new BasketTreeListView(this); m_tree->setHeaderLabel(i18n("Baskets")); m_tree->setSortingEnabled(false /*Disabled*/); m_tree->setRootIsDecorated(true); m_tree->setLineWidth(1); m_tree->setMidLineWidth(0); m_tree->setFocusPolicy(Qt::NoFocus); /// Configure the List View Drag and Drop: m_tree->setDragEnabled(true); m_tree->setDragDropMode(QAbstractItemView::DragDrop); m_tree->setAcceptDrops(true); m_tree->viewport()->setAcceptDrops(true); /// Configure the Splitter: m_stack = new QStackedWidget(this); setOpaqueResize(true); setCollapsible(indexOf(m_tree), true); setCollapsible(indexOf(m_stack), false); setStretchFactor(indexOf(m_tree), 0); setStretchFactor(indexOf(m_stack), 1); /// Configure the List View Signals: connect(m_tree, SIGNAL(itemActivated(QTreeWidgetItem *, int)), this, SLOT(slotPressed(QTreeWidgetItem *, int))); connect(m_tree, SIGNAL(itemPressed(QTreeWidgetItem *, int)), this, SLOT(slotPressed(QTreeWidgetItem *, int))); connect(m_tree, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, SLOT(slotPressed(QTreeWidgetItem *, int))); connect(m_tree, SIGNAL(itemExpanded(QTreeWidgetItem *)), this, SLOT(needSave(QTreeWidgetItem *))); connect(m_tree, SIGNAL(itemCollapsed(QTreeWidgetItem *)), this, SLOT(needSave(QTreeWidgetItem *))); connect(m_tree, SIGNAL(contextMenuRequested(const QPoint &)), this, SLOT(slotContextMenu(const QPoint &))); connect(m_tree, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(slotShowProperties(QTreeWidgetItem *))); connect(m_tree, SIGNAL(itemExpanded(QTreeWidgetItem *)), this, SIGNAL(basketChanged())); connect(m_tree, SIGNAL(itemCollapsed(QTreeWidgetItem *)), this, SIGNAL(basketChanged())); connect(this, SIGNAL(basketChanged()), this, SLOT(slotBasketChanged())); connect(m_history, SIGNAL(canRedoChanged(bool)), this, SLOT(canUndoRedoChanged())); connect(m_history, SIGNAL(canUndoChanged(bool)), this, SLOT(canUndoRedoChanged())); /* LikeBack */ Global::likeBack = new LikeBack(LikeBack::AllButtons, /*showBarByDefault=*/false, Global::config()); Global::likeBack->setServer("basket.linux62.org", "/likeback/send.php"); // There are too much comments, and people reading comments are more and more international, so we accept only English: // Global::likeBack->setAcceptedLanguages(QStringList::split(";", "en;fr"), i18n("Please write in English or French.")); // if (isPart()) // Global::likeBack->disableBar(); // See BNPView::shown() and BNPView::hide(). Global::likeBack->sendACommentAction(actionCollection()); // Just create it! setupActions(); /// What's This Help for the tree: m_tree->setWhatsThis( i18n("

Basket Tree

" "Here is the list of your baskets. " "You can organize your data by putting them in different baskets. " "You can group baskets by subject by creating new baskets inside others. " "You can browse between them by clicking a basket to open it, or reorganize them using drag and drop.")); setTreePlacement(Settings::treeOnLeft()); } void BNPView::setupActions() { QAction *a = NULL; KActionCollection *ac = actionCollection(); a = ac->addAction("basket_export_basket_archive", this, SLOT(saveAsArchive())); a->setText(i18n("&Basket Archive...")); a->setIcon(QIcon::fromTheme("baskets")); a->setShortcut(0); m_actSaveAsArchive = a; a = ac->addAction("basket_import_basket_archive", this, SLOT(openArchive())); a->setText(i18n("&Basket Archive...")); a->setIcon(QIcon::fromTheme("baskets")); a->setShortcut(0); m_actOpenArchive = a; a = ac->addAction("window_hide", this, SLOT(hideOnEscape())); a->setText(i18n("&Hide Window")); m_actionCollection->setDefaultShortcut(a, KStandardShortcut::Close); m_actHideWindow = a; m_actHideWindow->setEnabled(Settings::useSystray()); // Init here ! a = ac->addAction("basket_export_html", this, SLOT(exportToHTML())); a->setText(i18n("&HTML Web Page...")); a->setIcon(QIcon::fromTheme("text-html")); a->setShortcut(0); m_actExportToHtml = a; a = ac->addAction("basket_import_knotes", this, SLOT(importKNotes())); a->setText(i18n("K&Notes")); a->setIcon(QIcon::fromTheme("knotes")); a->setShortcut(0); a = ac->addAction("basket_import_kjots", this, SLOT(importKJots())); a->setText(i18n("K&Jots")); a->setIcon(QIcon::fromTheme("kjots")); a->setShortcut(0); a = ac->addAction("basket_import_knowit", this, SLOT(importKnowIt())); a->setText(i18n("&KnowIt...")); a->setIcon(QIcon::fromTheme("knowit")); a->setShortcut(0); a = ac->addAction("basket_import_tuxcards", this, SLOT(importTuxCards())); a->setText(i18n("Tux&Cards...")); a->setIcon(QIcon::fromTheme("tuxcards")); a->setShortcut(0); a = ac->addAction("basket_import_sticky_notes", this, SLOT(importStickyNotes())); a->setText(i18n("&Sticky Notes")); a->setIcon(QIcon::fromTheme("gnome")); a->setShortcut(0); a = ac->addAction("basket_import_tomboy", this, SLOT(importTomboy())); a->setText(i18n("&Tomboy")); a->setIcon(QIcon::fromTheme(IconNames::TOMBOY)); a->setShortcut(0); a = ac->addAction("basket_import_jreepad_file", this, SLOT(importJreepadFile())); a->setText(i18n("J&reepad XML File...")); a->setIcon(QIcon::fromTheme("text-xml")); a->setShortcut(0); a = ac->addAction("basket_import_text_file", this, SLOT(importTextFile())); a->setText(i18n("Text &File...")); a->setIcon(QIcon::fromTheme("text-plain")); a->setShortcut(0); a = ac->addAction("basket_backup_restore", this, SLOT(backupRestore())); a->setText(i18n("&Backup && Restore...")); a->setShortcut(0); a = ac->addAction("check_cleanup", this, SLOT(checkCleanup())); a->setText(i18n("&Check && Cleanup...")); a->setShortcut(0); if (Global::commandLineOpts->isSet("debug")) { a->setEnabled(true); } else { a->setEnabled(false); } /** Note : ****************************************************************/ a = ac->addAction("edit_delete", this, SLOT(delNote())); a->setText(i18n("D&elete")); a->setIcon(QIcon::fromTheme("edit-delete")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Delete")); m_actDelNote = a; m_actCutNote = ac->addAction(KStandardAction::Cut, this, SLOT(cutNote())); m_actCopyNote = ac->addAction(KStandardAction::Copy, this, SLOT(copyNote())); m_actSelectAll = ac->addAction(KStandardAction::SelectAll, this, SLOT(slotSelectAll())); m_actSelectAll->setStatusTip(i18n("Selects all notes")); a = ac->addAction("edit_unselect_all", this, SLOT(slotUnselectAll())); a->setText(i18n("U&nselect All")); m_actUnselectAll = a; m_actUnselectAll->setStatusTip(i18n("Unselects all selected notes")); a = ac->addAction("edit_invert_selection", this, SLOT(slotInvertSelection())); a->setText(i18n("&Invert Selection")); m_actionCollection->setDefaultShortcut(a, Qt::CTRL + Qt::Key_Asterisk); m_actInvertSelection = a; m_actInvertSelection->setStatusTip(i18n("Inverts the current selection of notes")); a = ac->addAction("note_edit", this, SLOT(editNote())); a->setText(i18nc("Verb; not Menu", "&Edit...")); // a->setIcon(QIcon::fromTheme("edit")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Return")); m_actEditNote = a; m_actOpenNote = ac->addAction(KStandardAction::Open, "note_open", this, SLOT(openNote())); m_actOpenNote->setIcon(QIcon::fromTheme("window-new")); m_actOpenNote->setText(i18n("&Open")); m_actionCollection->setDefaultShortcut(m_actOpenNote, QKeySequence("F9")); a = ac->addAction("note_open_with", this, SLOT(openNoteWith())); a->setText(i18n("Open &With...")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Shift+F9")); m_actOpenNoteWith = a; m_actSaveNoteAs = ac->addAction(KStandardAction::SaveAs, "note_save_to_file", this, SLOT(saveNoteAs())); m_actSaveNoteAs->setText(i18n("&Save to File...")); m_actionCollection->setDefaultShortcut(m_actSaveNoteAs, QKeySequence("F10")); a = ac->addAction("note_group", this, SLOT(noteGroup())); a->setText(i18n("&Group")); a->setIcon(QIcon::fromTheme("mail-attachment")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+G")); m_actGroup = a; a = ac->addAction("note_ungroup", this, SLOT(noteUngroup())); a->setText(i18n("U&ngroup")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+Shift+G")); m_actUngroup = a; a = ac->addAction("note_move_top", this, SLOT(moveOnTop())); a->setText(i18n("Move on &Top")); a->setIcon(QIcon::fromTheme("arrow-up-double")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+Shift+Home")); m_actMoveOnTop = a; a = ac->addAction("note_move_up", this, SLOT(moveNoteUp())); a->setText(i18n("Move &Up")); a->setIcon(QIcon::fromTheme("arrow-up")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+Shift+Up")); m_actMoveNoteUp = a; a = ac->addAction("note_move_down", this, SLOT(moveNoteDown())); a->setText(i18n("Move &Down")); a->setIcon(QIcon::fromTheme("arrow-down")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+Shift+Down")); m_actMoveNoteDown = a; a = ac->addAction("note_move_bottom", this, SLOT(moveOnBottom())); a->setText(i18n("Move on &Bottom")); a->setIcon(QIcon::fromTheme("arrow-down-double")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+Shift+End")); m_actMoveOnBottom = a; m_actPaste = ac->addAction(KStandardAction::Paste, this, SLOT(pasteInCurrentBasket())); /** Insert : **************************************************************/ QSignalMapper *insertEmptyMapper = new QSignalMapper(this); QSignalMapper *insertWizardMapper = new QSignalMapper(this); connect(insertEmptyMapper, SIGNAL(mapped(int)), this, SLOT(insertEmpty(int))); connect(insertWizardMapper, SIGNAL(mapped(int)), this, SLOT(insertWizard(int))); #if 0 a = ac->addAction("insert_text"); a->setText(i18n("Plai&n Text")); a->setIcon(QIcon::fromTheme("text")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+T")); m_actInsertText = a; #endif a = ac->addAction("insert_html"); a->setText(i18n("&Text")); a->setIcon(QIcon::fromTheme("text-html")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Insert")); m_actInsertHtml = a; a = ac->addAction("insert_link"); a->setText(i18n("&Link")); a->setIcon(QIcon::fromTheme(IconNames::LINK)); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+Y")); m_actInsertLink = a; a = ac->addAction("insert_cross_reference"); a->setText(i18n("Cross &Reference")); a->setIcon(QIcon::fromTheme(IconNames::CROSS_REF)); m_actInsertCrossReference = a; a = ac->addAction("insert_image"); a->setText(i18n("&Image")); a->setIcon(QIcon::fromTheme(IconNames::IMAGE)); m_actInsertImage = a; a = ac->addAction("insert_color"); a->setText(i18n("&Color")); a->setIcon(QIcon::fromTheme(IconNames::COLOR)); m_actInsertColor = a; a = ac->addAction("insert_launcher"); a->setText(i18n("L&auncher")); a->setIcon(QIcon::fromTheme(IconNames::LAUNCH)); m_actInsertLauncher = a; a = ac->addAction("insert_kmenu"); a->setText(i18n("Import Launcher for &desktop application...")); a->setIcon(QIcon::fromTheme(IconNames::KMENU)); m_actImportKMenu = a; a = ac->addAction("insert_icon"); a->setText(i18n("Im&port Icon...")); a->setIcon(QIcon::fromTheme(IconNames::ICONS)); m_actImportIcon = a; a = ac->addAction("insert_from_file"); a->setText(i18n("Load From &File...")); a->setIcon(QIcon::fromTheme(IconNames::DOCUMENT_IMPORT)); m_actLoadFile = a; // connect( m_actInsertText, SIGNAL(triggered()), insertEmptyMapper, SLOT(map()) ); connect(m_actInsertHtml, SIGNAL(triggered()), insertEmptyMapper, SLOT(map())); connect(m_actInsertImage, SIGNAL(triggered()), insertEmptyMapper, SLOT(map())); connect(m_actInsertLink, SIGNAL(triggered()), insertEmptyMapper, SLOT(map())); connect(m_actInsertCrossReference, SIGNAL(triggered()), insertEmptyMapper, SLOT(map())); connect(m_actInsertColor, SIGNAL(triggered()), insertEmptyMapper, SLOT(map())); connect(m_actInsertLauncher, SIGNAL(triggered()), insertEmptyMapper, SLOT(map())); // insertEmptyMapper->setMapping(m_actInsertText, NoteType::Text ); insertEmptyMapper->setMapping(m_actInsertHtml, NoteType::Html); insertEmptyMapper->setMapping(m_actInsertImage, NoteType::Image); insertEmptyMapper->setMapping(m_actInsertLink, NoteType::Link); insertEmptyMapper->setMapping(m_actInsertCrossReference, NoteType::CrossReference); insertEmptyMapper->setMapping(m_actInsertColor, NoteType::Color); insertEmptyMapper->setMapping(m_actInsertLauncher, NoteType::Launcher); connect(m_actImportKMenu, SIGNAL(triggered()), insertWizardMapper, SLOT(map())); connect(m_actImportIcon, SIGNAL(triggered()), insertWizardMapper, SLOT(map())); connect(m_actLoadFile, SIGNAL(triggered()), insertWizardMapper, SLOT(map())); insertWizardMapper->setMapping(m_actImportKMenu, 1); insertWizardMapper->setMapping(m_actImportIcon, 2); insertWizardMapper->setMapping(m_actLoadFile, 3); m_colorPicker = new DesktopColorPicker(); a = ac->addAction("insert_screen_color", this, SLOT(slotColorFromScreen())); a->setText(i18n("C&olor from Screen")); a->setIcon(QIcon::fromTheme("kcolorchooser")); m_actColorPicker = a; connect(m_colorPicker, SIGNAL(pickedColor(const QColor &)), this, SLOT(colorPicked(const QColor &))); connect(m_colorPicker, SIGNAL(canceledPick()), this, SLOT(colorPickingCanceled())); a = ac->addAction("insert_screen_capture", this, SLOT(grabScreenshot())); a->setText(i18n("Grab Screen &Zone")); a->setIcon(QIcon::fromTheme("ksnapshot")); m_actGrabScreenshot = a; // connect( m_actGrabScreenshot, SIGNAL(regionGrabbed(const QPixmap&)), this, SLOT(screenshotGrabbed(const QPixmap&)) ); // connect( m_colorPicker, SIGNAL(canceledPick()), this, SLOT(colorPickingCanceled()) ); // m_insertActions.append( m_actInsertText ); m_insertActions.append(m_actInsertHtml); m_insertActions.append(m_actInsertLink); m_insertActions.append(m_actInsertCrossReference); m_insertActions.append(m_actInsertImage); m_insertActions.append(m_actInsertColor); m_insertActions.append(m_actImportKMenu); m_insertActions.append(m_actInsertLauncher); m_insertActions.append(m_actImportIcon); m_insertActions.append(m_actLoadFile); m_insertActions.append(m_actColorPicker); m_insertActions.append(m_actGrabScreenshot); /** Basket : **************************************************************/ // At this stage, main.cpp has not set qApp->mainWidget(), so Global::runInsideKontact() // returns true. We do it ourself: bool runInsideKontact = true; QWidget *parentWidget = (QWidget *)parent(); while (parentWidget) { if (parentWidget->inherits("MainWindow")) runInsideKontact = false; parentWidget = (QWidget *)parentWidget->parent(); } // Use the "basket" icon in Kontact so it is consistent with the Kontact "New..." icon a = ac->addAction("basket_new", this, SLOT(askNewBasket())); a->setText(i18n("&New Basket...")); a->setIcon(QIcon::fromTheme((runInsideKontact ? "basket" : "document-new"))); m_actionCollection->setDefaultShortcuts(a, KStandardShortcut::shortcut(KStandardShortcut::New)); actNewBasket = a; a = ac->addAction("basket_new_sub", this, SLOT(askNewSubBasket())); a->setText(i18n("New &Sub-Basket...")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+Shift+N")); actNewSubBasket = a; a = ac->addAction("basket_new_sibling", this, SLOT(askNewSiblingBasket())); a->setText(i18n("New Si&bling Basket...")); actNewSiblingBasket = a; KActionMenu *newBasketMenu = new KActionMenu(i18n("&New"), ac); newBasketMenu->setIcon(QIcon::fromTheme("document-new")); ac->addAction("basket_new_menu", newBasketMenu); newBasketMenu->addAction(actNewBasket); newBasketMenu->addAction(actNewSubBasket); newBasketMenu->addAction(actNewSiblingBasket); connect(newBasketMenu, SIGNAL(triggered()), this, SLOT(askNewBasket())); a = ac->addAction("basket_properties", this, SLOT(propBasket())); a->setText(i18n("&Properties...")); a->setIcon(QIcon::fromTheme("document-properties")); m_actionCollection->setDefaultShortcut(a, QKeySequence("F2")); m_actPropBasket = a; a = ac->addAction("basket_sort_children_asc", this, SLOT(sortChildrenAsc())); a->setText(i18n("Sort Children Ascending")); a->setIcon(QIcon::fromTheme("view-sort-ascending")); m_actSortChildrenAsc = a; a = ac->addAction("basket_sort_children_desc", this, SLOT(sortChildrenDesc())); a->setText(i18n("Sort Children Descending")); a->setIcon(QIcon::fromTheme("view-sort-descending")); m_actSortChildrenDesc = a; a = ac->addAction("basket_sort_siblings_asc", this, SLOT(sortSiblingsAsc())); a->setText(i18n("Sort Siblings Ascending")); a->setIcon(QIcon::fromTheme("view-sort-ascending")); m_actSortSiblingsAsc = a; a = ac->addAction("basket_sort_siblings_desc", this, SLOT(sortSiblingsDesc())); a->setText(i18n("Sort Siblings Descending")); a->setIcon(QIcon::fromTheme("view-sort-descending")); m_actSortSiblingsDesc = a; a = ac->addAction("basket_remove", this, SLOT(delBasket())); a->setText(i18nc("Remove Basket", "&Remove")); a->setShortcut(0); m_actDelBasket = a; #ifdef HAVE_LIBGPGME a = ac->addAction("basket_password", this, SLOT(password())); a->setText(i18nc("Password protection", "Pass&word...")); a->setShortcut(0); m_actPassBasket = a; a = ac->addAction("basket_lock", this, SLOT(lockBasket())); a->setText(i18nc("Lock Basket", "&Lock")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+L")); m_actLockBasket = a; #endif /** Edit : ****************************************************************/ // m_actUndo = KStandardAction::undo( this, SLOT(undo()), actionCollection() ); // m_actUndo->setEnabled(false); // Not yet implemented ! // m_actRedo = KStandardAction::redo( this, SLOT(redo()), actionCollection() ); // m_actRedo->setEnabled(false); // Not yet implemented ! KToggleAction *toggleAct = NULL; toggleAct = new KToggleAction(i18n("&Filter"), ac); ac->addAction("edit_filter", toggleAct); toggleAct->setIcon(QIcon::fromTheme("view-filter")); m_actionCollection->setDefaultShortcuts(toggleAct, KStandardShortcut::shortcut(KStandardShortcut::Find)); m_actShowFilter = toggleAct; connect(m_actShowFilter, SIGNAL(toggled(bool)), this, SLOT(showHideFilterBar(bool))); toggleAct = new KToggleAction(ac); ac->addAction("edit_filter_all_baskets", toggleAct); toggleAct->setText(i18n("&Search All")); toggleAct->setIcon(QIcon::fromTheme("edit-find")); m_actionCollection->setDefaultShortcut(toggleAct, QKeySequence("Ctrl+Shift+F")); m_actFilterAllBaskets = toggleAct; connect(m_actFilterAllBaskets, SIGNAL(toggled(bool)), this, SLOT(toggleFilterAllBaskets(bool))); a = ac->addAction("edit_filter_reset", this, SLOT(slotResetFilter())); a->setText(i18n("&Reset Filter")); a->setIcon(QIcon::fromTheme("edit-clear-locationbar-rtl")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+R")); m_actResetFilter = a; /** Go : ******************************************************************/ a = ac->addAction("go_basket_previous", this, SLOT(goToPreviousBasket())); a->setText(i18n("&Previous Basket")); a->setIcon(QIcon::fromTheme("go-previous")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Alt+Left")); m_actPreviousBasket = a; a = ac->addAction("go_basket_next", this, SLOT(goToNextBasket())); a->setText(i18n("&Next Basket")); a->setIcon(QIcon::fromTheme("go-next")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Alt+Right")); m_actNextBasket = a; a = ac->addAction("go_basket_fold", this, SLOT(foldBasket())); a->setText(i18n("&Fold Basket")); a->setIcon(QIcon::fromTheme("go-up")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Alt+Up")); m_actFoldBasket = a; a = ac->addAction("go_basket_expand", this, SLOT(expandBasket())); a->setText(i18n("&Expand Basket")); a->setIcon(QIcon::fromTheme("go-down")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Alt+Down")); m_actExpandBasket = a; #if 0 // FOR_BETA_PURPOSE: a = ac->addAction("beta_convert_texts", this, SLOT(convertTexts())); a->setText(i18n("Convert text notes to rich text notes")); a->setIcon(QIcon::fromTheme("run-build-file")); m_convertTexts = a; #endif InlineEditors::instance()->initToolBars(actionCollection()); /** Help : ****************************************************************/ a = ac->addAction("help_welcome_baskets", this, SLOT(addWelcomeBaskets())); a->setText(i18n("&Welcome Baskets")); } BasketListViewItem *BNPView::topLevelItem(int i) { return (BasketListViewItem *)m_tree->topLevelItem(i); } void BNPView::slotShowProperties(QTreeWidgetItem *item) { if (item) propBasket(); } void BNPView::slotContextMenu(const QPoint &pos) { QTreeWidgetItem *item; item = m_tree->itemAt(pos); QString menuName; if (item) { BasketScene *basket = ((BasketListViewItem *)item)->basket(); setCurrentBasket(basket); menuName = "basket_popup"; } else { menuName = "tab_bar_popup"; /* * "File -> New" create a new basket with the same parent basket as the current one. * But when invoked when right-clicking the empty area at the bottom of the basket tree, * it is obvious the user want to create a new basket at the bottom of the tree (with no parent). * So we set a temporary variable during the time the popup menu is shown, * so the slot askNewBasket() will do the right thing: */ setNewBasketPopup(); } QMenu *menu = popupMenu(menuName); connect(menu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideNewBasketPopup())); menu->exec(m_tree->mapToGlobal(pos)); } /* this happens every time we switch the basket (but not if we tell the user we save the stuff */ void BNPView::save() { DEBUG_WIN << "Basket Tree: Saving..."; QString data; QXmlStreamWriter stream(&data); XMLWork::setupXmlStream(stream, "basketTree"); // Save Basket Tree: save(m_tree, 0, stream); stream.writeEndElement(); stream.writeEndDocument(); // Write to Disk: BasketScene::safelySaveToFile(Global::basketsFolder() + "baskets.xml", data); GitWrapper::commitBasketView(); } void BNPView::save(QTreeWidget *listView, QTreeWidgetItem *item, QXmlStreamWriter &stream) { if (item == 0) { if (listView == NULL) { // This should not happen: we call either save(listView, 0) or save(0, item) DEBUG_WIN << "BNPView::save error: listView=NULL and item=NULL"; return; } // For each basket: for (int i = 0; i < listView->topLevelItemCount(); i++) { item = listView->topLevelItem(i); save(0, item, stream); } } else { saveSubHierarchy(item, stream, true); } } void BNPView::writeBasketElement(QTreeWidgetItem *item, QXmlStreamWriter &stream) { BasketScene *basket = ((BasketListViewItem *)item)->basket(); // Save Attributes: stream.writeAttribute("folderName", basket->folderName()); if (item->childCount() >= 0) // If it can be expanded/folded: stream.writeAttribute("folded", XMLWork::trueOrFalse(!item->isExpanded())); if (((BasketListViewItem *)item)->isCurrentBasket()) stream.writeAttribute("lastOpened", "true"); basket->saveProperties(stream); } void BNPView::saveSubHierarchy(QTreeWidgetItem *item, QXmlStreamWriter &stream, bool recursive) { stream.writeStartElement("basket"); writeBasketElement(item, stream); // create root if (recursive) { for (int i = 0; i < item->childCount(); i++) { saveSubHierarchy(item->child(i), stream, true); } } stream.writeEndElement(); } void BNPView::load() { QScopedPointer doc(XMLWork::openFile("basketTree", Global::basketsFolder() + "baskets.xml")); // BEGIN Compatibility with 0.6.0 Pre-Alpha versions: if (!doc) doc.reset(XMLWork::openFile("basketsTree", Global::basketsFolder() + "baskets.xml")); // END if (doc != 0) { QDomElement docElem = doc->documentElement(); load(0L, docElem); } m_loading = false; } void BNPView::load(QTreeWidgetItem *item, const QDomElement &baskets) { QDomNode n = baskets.firstChild(); while (!n.isNull()) { QDomElement element = n.toElement(); if ((!element.isNull()) && element.tagName() == "basket") { QString folderName = element.attribute("folderName"); if (!folderName.isEmpty()) { BasketScene *basket = loadBasket(folderName); BasketListViewItem *basketItem = appendBasket(basket, item); basketItem->setExpanded(!XMLWork::trueOrFalse(element.attribute("folded", "false"), false)); basket->loadProperties(XMLWork::getElement(element, "properties")); if (XMLWork::trueOrFalse(element.attribute("lastOpened", element.attribute("lastOpened", "false")), false)) // Compat with 0.6.0-Alphas setCurrentBasket(basket); // Load Sub-baskets: load(basketItem, element); } } n = n.nextSibling(); } } BasketScene *BNPView::loadBasket(const QString &folderName) { if (folderName.isEmpty()) return 0; DecoratedBasket *decoBasket = new DecoratedBasket(m_stack, folderName); BasketScene *basket = decoBasket->basket(); m_stack->addWidget(decoBasket); connect(basket, SIGNAL(countsChanged(BasketScene *)), this, SLOT(countsChanged(BasketScene *))); // Important: Create listViewItem and connect signal BEFORE loadProperties(), so we get the listViewItem updated without extra work: connect(basket, SIGNAL(propertiesChanged(BasketScene *)), this, SLOT(updateBasketListViewItem(BasketScene *))); connect(basket->decoration()->filterBar(), SIGNAL(newFilter(const FilterData &)), this, SLOT(newFilterFromFilterBar())); connect(basket, SIGNAL(crossReference(QString)), this, SLOT(loadCrossReference(QString))); return basket; } int BNPView::basketCount(QTreeWidgetItem *parent) { int count = 1; if (parent == NULL) return 0; for (int i = 0; i < parent->childCount(); i++) { count += basketCount(parent->child(i)); } return count; } bool BNPView::canFold() { BasketListViewItem *item = listViewItemForBasket(currentBasket()); if (!item) return false; return (item->childCount() > 0 && item->isExpanded()); } bool BNPView::canExpand() { BasketListViewItem *item = listViewItemForBasket(currentBasket()); if (!item) return false; return (item->childCount() > 0 && !item->isExpanded()); } BasketListViewItem *BNPView::appendBasket(BasketScene *basket, QTreeWidgetItem *parentItem) { BasketListViewItem *newBasketItem; if (parentItem) newBasketItem = new BasketListViewItem(parentItem, parentItem->child(parentItem->childCount() - 1), basket); else { newBasketItem = new BasketListViewItem(m_tree, m_tree->topLevelItem(m_tree->topLevelItemCount() - 1), basket); } return newBasketItem; } void BNPView::loadNewBasket(const QString &folderName, const QDomElement &properties, BasketScene *parent) { BasketScene *basket = loadBasket(folderName); appendBasket(basket, (basket ? listViewItemForBasket(parent) : 0)); basket->loadProperties(properties); setCurrentBasketInHistory(basket); // save(); } int BNPView::topLevelItemCount() { return m_tree->topLevelItemCount(); } void BNPView::goToPreviousBasket() { if (m_history->canUndo()) m_history->undo(); } void BNPView::goToNextBasket() { if (m_history->canRedo()) m_history->redo(); } void BNPView::foldBasket() { BasketListViewItem *item = listViewItemForBasket(currentBasket()); if (item && item->childCount() <= 0) item->setExpanded(false); // If Alt+Left is hit and there is nothing to close, make sure the focus will go to the parent basket QKeyEvent *keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Left, 0, 0); QApplication::postEvent(m_tree, keyEvent); } void BNPView::expandBasket() { QKeyEvent *keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Right, 0, 0); QApplication::postEvent(m_tree, keyEvent); } void BNPView::closeAllEditors() { QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = (BasketListViewItem *)(*it); item->basket()->closeEditor(); ++it; } } bool BNPView::convertTexts() { bool convertedNotes = false; QProgressDialog dialog; dialog.setWindowTitle(i18n("Plain Text Notes Conversion")); dialog.setLabelText(i18n("Converting plain text notes to rich text ones...")); dialog.setModal(true); dialog.setRange(0, basketCount()); dialog.show(); // setMinimumDuration(50/*ms*/); QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = (BasketListViewItem *)(*it); if (item->basket()->convertTexts()) convertedNotes = true; dialog.setValue(dialog.value() + 1); if (dialog.wasCanceled()) break; ++it; } return convertedNotes; } void BNPView::toggleFilterAllBaskets(bool doFilter) { // If the filter isn't already showing, we make sure it does. if (doFilter) m_actShowFilter->setChecked(true); // currentBasket()->decoration()->filterBar()->setFilterAll(doFilter); if (doFilter) currentBasket()->decoration()->filterBar()->setEditFocus(); // Filter every baskets: newFilter(); } /** This function can be called recursively because we call qApp->processEvents(). * If this function is called whereas another "instance" is running, * this new "instance" leave and set up a flag that is read by the first "instance" * to know it should re-begin the work. * PS: Yes, that's a very lame pseudo-threading but that works, and it's programmer-efforts cheap :-) */ void BNPView::newFilter() { static bool alreadyEntered = false; static bool shouldRestart = false; if (alreadyEntered) { shouldRestart = true; return; } alreadyEntered = true; shouldRestart = false; BasketScene *current = currentBasket(); const FilterData &filterData = current->decoration()->filterBar()->filterData(); // Set the filter data for every other baskets, or reset the filter for every other baskets if we just disabled the filterInAllBaskets: QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); if (item->basket() != current) { if (isFilteringAllBaskets()) item->basket()->decoration()->filterBar()->setFilterData(filterData); // Set the new FilterData for every other baskets else item->basket()->decoration()->filterBar()->setFilterData(FilterData()); // We just disabled the global filtering: remove the FilterData } ++it; } // Show/hide the "little filter icons" (during basket load) // or the "little numbers" (to show number of found notes in the baskets) is the tree: qApp->processEvents(); // Load every baskets for filtering, if they are not already loaded, and if necessary: if (filterData.isFiltering) { BasketScene *current = currentBasket(); QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); if (item->basket() != current) { BasketScene *basket = item->basket(); if (!basket->loadingLaunched() && !basket->isLocked()) basket->load(); basket->filterAgain(); qApp->processEvents(); if (shouldRestart) { alreadyEntered = false; shouldRestart = false; newFilter(); return; } } ++it; } } // qApp->processEvents(); m_tree->viewport()->update(); // to see the "little numbers" alreadyEntered = false; shouldRestart = false; } void BNPView::newFilterFromFilterBar() { if (isFilteringAllBaskets()) QTimer::singleShot(0, this, SLOT(newFilter())); // Keep time for the QLineEdit to display the filtered character and refresh correctly! } bool BNPView::isFilteringAllBaskets() { return m_actFilterAllBaskets->isChecked(); } BasketListViewItem *BNPView::listViewItemForBasket(BasketScene *basket) { QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); if (item->basket() == basket) return item; ++it; } return 0L; } BasketScene *BNPView::currentBasket() { DecoratedBasket *decoBasket = (DecoratedBasket *)m_stack->currentWidget(); if (decoBasket) return decoBasket->basket(); else return 0; } BasketScene *BNPView::parentBasketOf(BasketScene *basket) { BasketListViewItem *item = (BasketListViewItem *)(listViewItemForBasket(basket)->parent()); if (item) return item->basket(); else return 0; } void BNPView::setCurrentBasketInHistory(BasketScene *basket) { if (!basket) return; if (currentBasket() == basket) return; m_history->push(new HistorySetBasket(basket)); } void BNPView::setCurrentBasket(BasketScene *basket) { if (currentBasket() == basket) return; if (currentBasket()) currentBasket()->closeBasket(); if (basket) basket->aboutToBeActivated(); BasketListViewItem *item = listViewItemForBasket(basket); if (item) { m_tree->setCurrentItem(item); item->ensureVisible(); m_stack->setCurrentWidget(basket->decoration()); // If the window has changed size, only the current basket receive the event, // the others will receive ony one just before they are shown. // But this triggers unwanted animations, so we eliminate it: basket->relayoutNotes(/*animate=*/false); basket->openBasket(); setWindowTitle(item->basket()->basketName()); countsChanged(basket); updateStatusBarHint(); if (Global::systemTray) Global::systemTray->updateDisplay(); m_tree->scrollToItem(m_tree->currentItem()); item->basket()->setFocus(); } m_tree->viewport()->update(); emit basketChanged(); } void BNPView::removeBasket(BasketScene *basket) { if (basket->isDuringEdit()) basket->closeEditor(); // Find a new basket to switch to and select it. // Strategy: get the next sibling, or the previous one if not found. // If there is no such one, get the parent basket: BasketListViewItem *basketItem = listViewItemForBasket(basket); BasketListViewItem *nextBasketItem = (BasketListViewItem *)(m_tree->itemBelow(basketItem)); if (!nextBasketItem) nextBasketItem = (BasketListViewItem *)m_tree->itemAbove(basketItem); if (!nextBasketItem) nextBasketItem = (BasketListViewItem *)(basketItem->parent()); if (nextBasketItem) setCurrentBasketInHistory(nextBasketItem->basket()); // Remove from the view: basket->unsubscribeBackgroundImages(); m_stack->removeWidget(basket->decoration()); // delete basket->decoration(); delete basketItem; // delete basket; // If there is no basket anymore, add a new one: if (!nextBasketItem) BasketFactory::newBasket(/*icon=*/"", /*name=*/i18n("General"), /*backgroundImage=*/"", /*backgroundColor=*/QColor(), /*textColor=*/QColor(), /*templateName=*/"1column", /*createIn=*/0); else // No need to save two times if we add a basket save(); } void BNPView::setTreePlacement(bool onLeft) { if (onLeft) insertWidget(0, m_tree); else addWidget(m_tree); // updateGeometry(); qApp->postEvent(this, new QResizeEvent(size(), size())); } void BNPView::relayoutAllBaskets() { QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); // item->basket()->unbufferizeAll(); item->basket()->unsetNotesWidth(); item->basket()->relayoutNotes(true); ++it; } } void BNPView::recomputeAllStyles() { QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); item->basket()->recomputeAllStyles(); item->basket()->unsetNotesWidth(); item->basket()->relayoutNotes(true); ++it; } } void BNPView::removedStates(const QList &deletedStates) { QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); item->basket()->removedStates(deletedStates); ++it; } } void BNPView::linkLookChanged() { QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); item->basket()->linkLookChanged(); ++it; } } void BNPView::filterPlacementChanged(bool onTop) { QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = static_cast(*it); DecoratedBasket *decoration = static_cast(item->basket()->parent()); decoration->setFilterBarPosition(onTop); ++it; } } void BNPView::updateBasketListViewItem(BasketScene *basket) { BasketListViewItem *item = listViewItemForBasket(basket); if (item) item->setup(); if (basket == currentBasket()) { setWindowTitle(basket->basketName()); if (Global::systemTray) Global::systemTray->updateDisplay(); } // Don't save if we are loading! if (!m_loading) save(); } void BNPView::needSave(QTreeWidgetItem *) { if (!m_loading) // A basket has been collapsed/expanded or a new one is select: this is not urgent: QTimer::singleShot(500 /*ms*/, this, SLOT(save())); } void BNPView::slotPressed(QTreeWidgetItem *item, int column) { Q_UNUSED(column); BasketScene *basket = currentBasket(); if (basket == 0) return; // Impossible to Select no Basket: if (!item) m_tree->setCurrentItem(listViewItemForBasket(basket), true); else if (dynamic_cast(item) != 0 && currentBasket() != ((BasketListViewItem *)item)->basket()) { setCurrentBasketInHistory(((BasketListViewItem *)item)->basket()); needSave(0); } basket->graphicsView()->viewport()->setFocus(); } DecoratedBasket *BNPView::currentDecoratedBasket() { if (currentBasket()) return currentBasket()->decoration(); else return 0; } // Redirected actions : void BNPView::exportToHTML() { HTMLExporter exporter(currentBasket()); } void BNPView::editNote() { currentBasket()->noteEdit(); } void BNPView::cutNote() { currentBasket()->noteCut(); } void BNPView::copyNote() { currentBasket()->noteCopy(); } void BNPView::delNote() { currentBasket()->noteDelete(); } void BNPView::openNote() { currentBasket()->noteOpen(); } void BNPView::openNoteWith() { currentBasket()->noteOpenWith(); } void BNPView::saveNoteAs() { currentBasket()->noteSaveAs(); } void BNPView::noteGroup() { currentBasket()->noteGroup(); } void BNPView::noteUngroup() { currentBasket()->noteUngroup(); } void BNPView::moveOnTop() { currentBasket()->noteMoveOnTop(); } void BNPView::moveOnBottom() { currentBasket()->noteMoveOnBottom(); } void BNPView::moveNoteUp() { currentBasket()->noteMoveNoteUp(); } void BNPView::moveNoteDown() { currentBasket()->noteMoveNoteDown(); } void BNPView::slotSelectAll() { currentBasket()->selectAll(); } void BNPView::slotUnselectAll() { currentBasket()->unselectAll(); } void BNPView::slotInvertSelection() { currentBasket()->invertSelection(); } void BNPView::slotResetFilter() { currentDecoratedBasket()->resetFilter(); } void BNPView::importKJots() { SoftwareImporters::importKJots(); } void BNPView::importKNotes() { SoftwareImporters::importKNotes(); } void BNPView::importKnowIt() { SoftwareImporters::importKnowIt(); } void BNPView::importTuxCards() { SoftwareImporters::importTuxCards(); } void BNPView::importStickyNotes() { SoftwareImporters::importStickyNotes(); } void BNPView::importTomboy() { SoftwareImporters::importTomboy(); } void BNPView::importJreepadFile() { SoftwareImporters::importJreepadFile(); } void BNPView::importTextFile() { SoftwareImporters::importTextFile(); } void BNPView::backupRestore() { BackupDialog dialog; dialog.exec(); } void checkNote(Note *note, QList &fileList) { while (note) { note->finishLazyLoad(); if (note->isGroup()) { checkNote(note->firstChild(), fileList); } else if (note->content()->useFile()) { QString noteFileName = note->basket()->folderName() + note->content()->fileName(); int basketFileIndex = fileList.indexOf(noteFileName); if (basketFileIndex < 0) { DEBUG_WIN << "" + noteFileName + " NOT FOUND!"; } else { fileList.removeAt(basketFileIndex); } } note = note->next(); } } void checkBasket(BasketListViewItem *item, QList &dirList, QList &fileList) { BasketScene *basket = ((BasketListViewItem *)item)->basket(); QString basketFolderName = basket->folderName(); int basketFolderIndex = dirList.indexOf(basket->folderName()); if (basketFolderIndex < 0) { DEBUG_WIN << "" + basketFolderName + " NOT FOUND!"; } else { dirList.removeAt(basketFolderIndex); } int basketFileIndex = fileList.indexOf(basket->folderName() + ".basket"); if (basketFileIndex < 0) { DEBUG_WIN << ".basket file of " + basketFolderName + ".basket NOT FOUND!"; } else { fileList.removeAt(basketFileIndex); } if (!basket->loadingLaunched() && !basket->isLocked()) { basket->load(); } DEBUG_WIN << "\t********************************************************************************"; DEBUG_WIN << basket->basketName() << "(" << basketFolderName << ") loaded."; Note *note = basket->firstNote(); if (!note) { DEBUG_WIN << "\tHas NO notes!"; } else { checkNote(note, fileList); } basket->save(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents, 100); for (int i = 0; i < item->childCount(); i++) { checkBasket((BasketListViewItem *)item->child(i), dirList, fileList); } if (basket != Global::bnpView->currentBasket()) { DEBUG_WIN << basket->basketName() << "(" << basketFolderName << ") unloading..."; DEBUG_WIN << "\t********************************************************************************"; basket->unbufferizeAll(); } else { DEBUG_WIN << basket->basketName() << "(" << basketFolderName << ") is the current basket, not unloading."; DEBUG_WIN << "\t********************************************************************************"; } qApp->processEvents(QEventLoop::ExcludeUserInputEvents, 100); } void BNPView::checkCleanup() { DEBUG_WIN << "Starting the check, cleanup and reindexing... (" + Global::basketsFolder() + ')'; QList dirList; QList fileList; QString topDirEntry; QString subDirEntry; QFileInfo fileInfo; QDir topDir(Global::basketsFolder(), QString(), QDir::Name | QDir::IgnoreCase, QDir::TypeMask | QDir::Hidden); foreach (topDirEntry, topDir.entryList()) { if (topDirEntry != "." && topDirEntry != "..") { fileInfo.setFile(Global::basketsFolder() + '/' + topDirEntry); if (fileInfo.isDir()) { dirList << topDirEntry + '/'; QDir basketDir(Global::basketsFolder() + '/' + topDirEntry, QString(), QDir::Name | QDir::IgnoreCase, QDir::TypeMask | QDir::Hidden); foreach (subDirEntry, basketDir.entryList()) { if (subDirEntry != "." && subDirEntry != "..") { fileList << topDirEntry + '/' + subDirEntry; } } } else if (topDirEntry != "." && topDirEntry != ".." && topDirEntry != "baskets.xml") { fileList << topDirEntry; } } } DEBUG_WIN << "Directories found: " + QString::number(dirList.count()); DEBUG_WIN << "Files found: " + QString::number(fileList.count()); DEBUG_WIN << "Checking Baskets:"; for (int i = 0; i < topLevelItemCount(); i++) { checkBasket(topLevelItem(i), dirList, fileList); } DEBUG_WIN << "Baskets checked."; DEBUG_WIN << "Directories remaining (not in any basket): " + QString::number(dirList.count()); DEBUG_WIN << "Files remaining (not in any basket): " + QString::number(fileList.count()); foreach (topDirEntry, dirList) { DEBUG_WIN << "" + topDirEntry + " does not belong to any basket!"; // Tools::deleteRecursively(Global::basketsFolder() + '/' + topDirEntry); // DEBUG_WIN << "\t" + topDirEntry + " removed!"; Tools::trashRecursively(Global::basketsFolder() + "/" + topDirEntry); DEBUG_WIN << "\t" + topDirEntry + " trashed!"; foreach (subDirEntry, fileList) { fileInfo.setFile(Global::basketsFolder() + '/' + subDirEntry); if (!fileInfo.isFile()) { fileList.removeAll(subDirEntry); DEBUG_WIN << "\t\t" + subDirEntry + " already removed!"; } } } foreach (subDirEntry, fileList) { DEBUG_WIN << "" + subDirEntry + " does not belong to any note!"; // Tools::deleteRecursively(Global::basketsFolder() + '/' + subDirEntry); // DEBUG_WIN << "\t" + subDirEntry + " removed!"; Tools::trashRecursively(Global::basketsFolder() + '/' + subDirEntry); DEBUG_WIN << "\t" + subDirEntry + " trashed!"; } DEBUG_WIN << "Check, cleanup and reindexing completed"; } void BNPView::countsChanged(BasketScene *basket) { if (basket == currentBasket()) notesStateChanged(); } void BNPView::notesStateChanged() { BasketScene *basket = currentBasket(); // Update statusbar message : if (currentBasket()->isLocked()) setSelectionStatus(i18n("Locked")); else if (!basket->isLoaded()) setSelectionStatus(i18n("Loading...")); else if (basket->count() == 0) setSelectionStatus(i18n("No notes")); else { QString count = i18np("%1 note", "%1 notes", basket->count()); QString selecteds = i18np("%1 selected", "%1 selected", basket->countSelecteds()); QString showns = (currentDecoratedBasket()->filterData().isFiltering ? i18n("all matches") : i18n("no filter")); if (basket->countFounds() != basket->count()) showns = i18np("%1 match", "%1 matches", basket->countFounds()); setSelectionStatus(i18nc("e.g. '18 notes, 10 matches, 5 selected'", "%1, %2, %3", count, showns, selecteds)); } if (currentBasket()->redirectEditActions()) { m_actSelectAll->setEnabled(!currentBasket()->selectedAllTextInEditor()); m_actUnselectAll->setEnabled(currentBasket()->hasSelectedTextInEditor()); } else { m_actSelectAll->setEnabled(basket->countSelecteds() < basket->countFounds()); m_actUnselectAll->setEnabled(basket->countSelecteds() > 0); } m_actInvertSelection->setEnabled(basket->countFounds() > 0); updateNotesActions(); } void BNPView::updateNotesActions() { bool isLocked = currentBasket()->isLocked(); bool oneSelected = currentBasket()->countSelecteds() == 1; bool oneOrSeveralSelected = currentBasket()->countSelecteds() >= 1; bool severalSelected = currentBasket()->countSelecteds() >= 2; // FIXME: m_actCheckNotes is also modified in void BNPView::areSelectedNotesCheckedChanged(bool checked) // bool BasketScene::areSelectedNotesChecked() should return false if bool BasketScene::showCheckBoxes() is false // m_actCheckNotes->setChecked( oneOrSeveralSelected && // currentBasket()->areSelectedNotesChecked() && // currentBasket()->showCheckBoxes() ); Note *selectedGroup = (severalSelected ? currentBasket()->selectedGroup() : 0); m_actEditNote->setEnabled(!isLocked && oneSelected && !currentBasket()->isDuringEdit()); if (currentBasket()->redirectEditActions()) { m_actCutNote->setEnabled(currentBasket()->hasSelectedTextInEditor()); m_actCopyNote->setEnabled(currentBasket()->hasSelectedTextInEditor()); m_actPaste->setEnabled(true); m_actDelNote->setEnabled(currentBasket()->hasSelectedTextInEditor()); } else { m_actCutNote->setEnabled(!isLocked && oneOrSeveralSelected); m_actCopyNote->setEnabled(oneOrSeveralSelected); m_actPaste->setEnabled(!isLocked); m_actDelNote->setEnabled(!isLocked && oneOrSeveralSelected); } m_actOpenNote->setEnabled(oneOrSeveralSelected); m_actOpenNoteWith->setEnabled(oneSelected); // TODO: oneOrSeveralSelected IF SAME TYPE m_actSaveNoteAs->setEnabled(oneSelected); // IDEM? m_actGroup->setEnabled(!isLocked && severalSelected && (!selectedGroup || selectedGroup->isColumn())); m_actUngroup->setEnabled(!isLocked && selectedGroup && !selectedGroup->isColumn()); m_actMoveOnTop->setEnabled(!isLocked && oneOrSeveralSelected && !currentBasket()->isFreeLayout()); m_actMoveNoteUp->setEnabled(!isLocked && oneOrSeveralSelected); // TODO: Disable when unavailable! m_actMoveNoteDown->setEnabled(!isLocked && oneOrSeveralSelected); m_actMoveOnBottom->setEnabled(!isLocked && oneOrSeveralSelected && !currentBasket()->isFreeLayout()); for (QList::const_iterator action = m_insertActions.constBegin(); action != m_insertActions.constEnd(); ++action) (*action)->setEnabled(!isLocked); // From the old Note::contextMenuEvent(...) : /* if (useFile() || m_type == Link) { m_type == Link ? i18n("&Open target") : i18n("&Open") m_type == Link ? i18n("Open target &with...") : i18n("Open &with...") m_type == Link ? i18n("&Save target as...") : i18n("&Save a copy as...") // If useFile() there is always a file to open / open with / save, but : if (m_type == Link) { if (url().toDisplayString().isEmpty() && runCommand().isEmpty()) // no URL nor runCommand : popupMenu->setItemEnabled(7, false); // no possible Open ! if (url().toDisplayString().isEmpty()) // no URL : popupMenu->setItemEnabled(8, false); // no possible Open with ! if (url().toDisplayString().isEmpty() || url().path().endsWith("/")) // no URL or target a folder : popupMenu->setItemEnabled(9, false); // not possible to save target file } } else if (m_type != Color) { popupMenu->insertSeparator(); popupMenu->insertItem( QIcon::fromTheme("document-save-as"), i18n("&Save a copy as..."), this, SLOT(slotSaveAs()), 0, 10 ); }*/ } // BEGIN Color picker (code from KColorEdit): /* Activate the mode */ void BNPView::slotColorFromScreen(bool global) { m_colorPickWasGlobal = global; hideMainWindow(); currentBasket()->saveInsertionData(); m_colorPicker->pickColor(); /* m_gettingColorFromScreen = true; qApp->processEvents(); QTimer::singleShot( 100, this, SLOT(grabColorFromScreen()) );*/ } void BNPView::slotColorFromScreenGlobal() { slotColorFromScreen(true); } void BNPView::colorPicked(const QColor &color) { if (!currentBasket()->isLoaded()) { showPassiveLoading(currentBasket()); currentBasket()->load(); } currentBasket()->insertColor(color); if (m_colorPickWasShown) showMainWindow(); if (Settings::usePassivePopup()) showPassiveDropped(i18n("Picked color to basket %1")); } void BNPView::colorPickingCanceled() { if (m_colorPickWasShown) showMainWindow(); } void BNPView::slotConvertTexts() { /* int result = KMessageBox::questionYesNoCancel( this, i18n( "

This will convert every text notes into rich text notes.
" "The content of the notes will not change and you will be able to apply formatting to those notes.

" "

This process cannot be reverted back: you will not be able to convert the rich text notes to plain text ones later.

" "

As a beta-tester, you are strongly encouraged to do the convert process because it is to test if plain text notes are still needed.
" "If nobody complain about not having plain text notes anymore, then the final version is likely to not support plain text notes anymore.

" "

Which basket notes do you want to convert?

" ), i18n("Convert Text Notes"), KGuiItem(i18n("Only in the Current Basket")), KGuiItem(i18n("In Every Baskets")) ); if (result == KMessageBox::Cancel) return; */ bool conversionsDone; // if (result == KMessageBox::Yes) // conversionsDone = currentBasket()->convertTexts(); // else conversionsDone = convertTexts(); if (conversionsDone) KMessageBox::information(this, i18n("The plain text notes have been converted to rich text."), i18n("Conversion Finished")); else KMessageBox::information(this, i18n("There are no plain text notes to convert."), i18n("Conversion Finished")); } QMenu *BNPView::popupMenu(const QString &menuName) { QMenu *menu = 0; bool hack = false; // TODO fix this // When running in kontact and likeback Information message is shown // factory is 0. Don't show error then and don't crash either :-) if (m_guiClient) { KXMLGUIFactory *factory = m_guiClient->factory(); if (factory) { menu = (QMenu *)factory->container(menuName, m_guiClient); } else hack = isPart(); } if (menu == 0) { if (!hack) { QString basketDataPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/basket/"; KMessageBox::error(this, i18n("

The file basketui.rc seems to not exist or is too old.
" "%1 cannot run without it and will stop.

" "

Please check your installation of %2.

" "

If you do not have administrator access to install the application " "system wide, you can copy the file basketui.rc from the installation " "archive to the folder %4.

" "

As last resort, if you are sure the application is correctly installed " "but you had a preview version of it, try to remove the " "file %5basketui.rc

", QGuiApplication::applicationDisplayName(), QGuiApplication::applicationDisplayName(), basketDataPath, basketDataPath, basketDataPath), i18n("Resource not Found"), KMessageBox::AllowLink); } if (!isPart()) exit(1); // We SHOULD exit right now and aboard everything because the caller except menu != 0 to not crash. else menu = new QMenu; // When running in kpart we cannot exit } return menu; } void BNPView::showHideFilterBar(bool show, bool switchFocus) { // if (show != m_actShowFilter->isChecked()) // m_actShowFilter->setChecked(show); m_actShowFilter->setChecked(show); currentDecoratedBasket()->setFilterBarVisible(show, switchFocus); if (!show) currentDecoratedBasket()->resetFilter(); } void BNPView::insertEmpty(int type) { if (currentBasket()->isLocked()) { showPassiveImpossible(i18n("Cannot add note.")); return; } currentBasket()->insertEmptyNote(type); } void BNPView::insertWizard(int type) { if (currentBasket()->isLocked()) { showPassiveImpossible(i18n("Cannot add note.")); return; } currentBasket()->insertWizard(type); } // BEGIN Screen Grabbing: void BNPView::grabScreenshot(bool global) { if (m_regionGrabber) { KWindowSystem::activateWindow(m_regionGrabber->winId()); return; } // Delay before to take a screenshot because if we hide the main window OR the systray popup menu, // we should wait the windows below to be repainted!!! // A special case is where the action is triggered with the global keyboard shortcut. // In this case, global is true, and we don't wait. // In the future, if global is also defined for other cases, check for // enum QAction::ActivationReason { UnknownActivation, EmulatedActivation, AccelActivation, PopupMenuActivation, ToolBarActivation }; int delay = (isMainWindowActive() ? 500 : (global /*qApp->activePopupWidget()*/ ? 0 : 200)); m_colorPickWasGlobal = global; hideMainWindow(); currentBasket()->saveInsertionData(); usleep(delay * 1000); m_regionGrabber = new RegionGrabber; connect(m_regionGrabber, SIGNAL(regionGrabbed(const QPixmap &)), this, SLOT(screenshotGrabbed(const QPixmap &))); } void BNPView::hideMainWindow() { if (isMainWindowActive()) { if (Global::activeMainWindow()) { m_HiddenMainWindow = Global::activeMainWindow(); m_HiddenMainWindow->hide(); } m_colorPickWasShown = true; } else m_colorPickWasShown = false; } void BNPView::grabScreenshotGlobal() { grabScreenshot(true); } void BNPView::screenshotGrabbed(const QPixmap &pixmap) { delete m_regionGrabber; m_regionGrabber = 0; // Cancelled (pressed Escape): if (pixmap.isNull()) { if (m_colorPickWasShown) showMainWindow(); return; } if (!currentBasket()->isLoaded()) { showPassiveLoading(currentBasket()); currentBasket()->load(); } currentBasket()->insertImage(pixmap); if (m_colorPickWasShown) showMainWindow(); if (Settings::usePassivePopup()) showPassiveDropped(i18n("Grabbed screen zone to basket %1")); } BasketScene *BNPView::basketForFolderName(const QString &folderName) { /* QPtrList basketsList = listBaskets(); BasketScene *basket; for (basket = basketsList.first(); basket; basket = basketsList.next()) if (basket->folderName() == folderName) return basket; */ QString name = folderName; if (!name.endsWith('/')) name += '/'; QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); if (item->basket()->folderName() == name) return item->basket(); ++it; } return 0; } Note *BNPView::noteForFileName(const QString &fileName, BasketScene &basket, Note *note) { if (!note) note = basket.firstNote(); if (note->fullPath().endsWith(fileName)) return note; Note *child = note->firstChild(); Note *found; while (child) { found = noteForFileName(fileName, basket, child); if (found) return found; child = child->next(); } return 0; } void BNPView::setFiltering(bool filtering) { m_actShowFilter->setChecked(filtering); m_actResetFilter->setEnabled(filtering); if (!filtering) m_actFilterAllBaskets->setEnabled(false); } void BNPView::undo() { // TODO } void BNPView::redo() { // TODO } void BNPView::pasteToBasket(int /*index*/, QClipboard::Mode /*mode*/) { // TODO: REMOVE! // basketAt(index)->pasteNote(mode); } void BNPView::propBasket() { BasketPropertiesDialog dialog(currentBasket(), this); dialog.exec(); } void BNPView::delBasket() { // DecoratedBasket *decoBasket = currentDecoratedBasket(); BasketScene *basket = currentBasket(); int really = KMessageBox::questionYesNo(this, i18n("Do you really want to remove the basket %1 and its contents?", Tools::textToHTMLWithoutP(basket->basketName())), i18n("Remove Basket"), KGuiItem(i18n("&Remove Basket"), "edit-delete"), KStandardGuiItem::cancel()); if (really == KMessageBox::No) return; QStringList basketsList = listViewItemForBasket(basket)->childNamesTree(0); if (basketsList.count() > 0) { int deleteChilds = KMessageBox::questionYesNoList(this, i18n("%1 has the following children baskets.
Do you want to remove them too?
", Tools::textToHTMLWithoutP(basket->basketName())), basketsList, i18n("Remove Children Baskets"), KGuiItem(i18n("&Remove Children Baskets"), "edit-delete")); if (deleteChilds == KMessageBox::No) return; } QString basketFolderName = basket->folderName(); doBasketDeletion(basket); GitWrapper::commitDeleteBasket(basketFolderName); } void BNPView::doBasketDeletion(BasketScene *basket) { basket->closeEditor(); QTreeWidgetItem *basketItem = listViewItemForBasket(basket); for (int i = 0; i < basketItem->childCount(); i++) { // First delete the child baskets: doBasketDeletion(((BasketListViewItem *)basketItem->child(i))->basket()); } // Then, basket have no child anymore, delete it: DecoratedBasket *decoBasket = basket->decoration(); basket->deleteFiles(); removeBasket(basket); // Remove the action to avoid keyboard-shortcut clashes: delete basket->m_action; // FIXME: It's quick&dirty. In the future, the Basket should be deleted, and then the QAction deleted in the Basket destructor. delete decoBasket; // delete basket; } void BNPView::password() { #ifdef HAVE_LIBGPGME QPointer dlg = new PasswordDlg(qApp->activeWindow()); BasketScene *cur = currentBasket(); dlg->setType(cur->encryptionType()); dlg->setKey(cur->encryptionKey()); if (dlg->exec()) { cur->setProtection(dlg->type(), dlg->key()); if (cur->encryptionType() != BasketScene::NoEncryption) { // Clear metadata Tools::deleteMetadataRecursively(cur->fullPath()); cur->lock(); } } #endif } void BNPView::lockBasket() { #ifdef HAVE_LIBGPGME BasketScene *cur = currentBasket(); cur->lock(); #endif } void BNPView::saveAsArchive() { BasketScene *basket = currentBasket(); QDir dir; KConfigGroup config = KSharedConfig::openConfig()->group("Basket Archive"); QString folder = config.readEntry("lastFolder", QDir::homePath()) + "/"; QString url = folder + QString(basket->basketName()).replace('/', '_') + ".baskets"; QString filter = "*.baskets|" + i18n("Basket Archives") + "\n*|" + i18n("All Files"); QString destination = url; for (bool askAgain = true; askAgain;) { destination = QFileDialog::getSaveFileName(NULL, i18n("Save as Basket Archive"), destination, filter); if (destination.isEmpty()) // User canceled return; if (dir.exists(destination)) { int result = KMessageBox::questionYesNoCancel( this, "" + i18n("The file %1 already exists. Do you really want to override it?", QUrl::fromLocalFile(destination).fileName()), i18n("Override File?"), KGuiItem(i18n("&Override"), "document-save")); if (result == KMessageBox::Cancel) return; else if (result == KMessageBox::Yes) askAgain = false; } else askAgain = false; } bool withSubBaskets = true; // KMessageBox::questionYesNo(this, i18n("Do you want to export sub-baskets too?"), i18n("Save as Basket Archive")) == KMessageBox::Yes; config.writeEntry("lastFolder", QUrl::fromLocalFile(destination).adjusted(QUrl::RemoveFilename).path()); config.sync(); Archive::save(basket, withSubBaskets, destination); } QString BNPView::s_fileToOpen = ""; void BNPView::delayedOpenArchive() { Archive::open(s_fileToOpen); } QString BNPView::s_basketToOpen = ""; void BNPView::delayedOpenBasket() { BasketScene *bv = this->basketForFolderName(s_basketToOpen); this->setCurrentBasketInHistory(bv); } void BNPView::openArchive() { QString filter = "*.baskets|" + i18n("Basket Archives") + "\n*|" + i18n("All Files"); QString path = QFileDialog::getOpenFileName(this, i18n("Open Basket Archive"), QString(), filter); if (!path.isEmpty()) // User has not canceled Archive::open(path); } void BNPView::activatedTagShortcut() { Tag *tag = Tag::tagForKAction((QAction *)sender()); currentBasket()->activatedTagShortcut(tag); } void BNPView::slotBasketChanged() { m_actFoldBasket->setEnabled(canFold()); m_actExpandBasket->setEnabled(canExpand()); if (currentBasket()->decoration()->filterData().isFiltering) currentBasket()->decoration()->filterBar()->show(); // especially important for Filter all setFiltering(currentBasket() && currentBasket()->decoration()->filterData().isFiltering); this->canUndoRedoChanged(); } void BNPView::canUndoRedoChanged() { if (m_history) { m_actPreviousBasket->setEnabled(m_history->canUndo()); m_actNextBasket->setEnabled(m_history->canRedo()); } } void BNPView::currentBasketChanged() { } void BNPView::isLockedChanged() { bool isLocked = currentBasket()->isLocked(); setLockStatus(isLocked); // m_actLockBasket->setChecked(isLocked); m_actPropBasket->setEnabled(!isLocked); m_actDelBasket->setEnabled(!isLocked); updateNotesActions(); } void BNPView::askNewBasket() { askNewBasket(0, 0); GitWrapper::commitCreateBasket(); } void BNPView::askNewBasket(BasketScene *parent, BasketScene *pickProperties) { NewBasketDefaultProperties properties; if (pickProperties) { properties.icon = pickProperties->icon(); properties.backgroundImage = pickProperties->backgroundImageName(); properties.backgroundColor = pickProperties->backgroundColorSetting(); properties.textColor = pickProperties->textColorSetting(); properties.freeLayout = pickProperties->isFreeLayout(); properties.columnCount = pickProperties->columnsCount(); } NewBasketDialog(parent, properties, this).exec(); } void BNPView::askNewSubBasket() { askNewBasket(/*parent=*/currentBasket(), /*pickPropertiesOf=*/currentBasket()); } void BNPView::askNewSiblingBasket() { askNewBasket(/*parent=*/parentBasketOf(currentBasket()), /*pickPropertiesOf=*/currentBasket()); } void BNPView::globalPasteInCurrentBasket() { currentBasket()->setInsertPopupMenu(); pasteInCurrentBasket(); currentBasket()->cancelInsertPopupMenu(); } void BNPView::pasteInCurrentBasket() { currentBasket()->pasteNote(); if (Settings::usePassivePopup()) showPassiveDropped(i18n("Clipboard content pasted to basket %1")); } void BNPView::pasteSelInCurrentBasket() { currentBasket()->pasteNote(QClipboard::Selection); if (Settings::usePassivePopup()) showPassiveDropped(i18n("Selection pasted to basket %1")); } void BNPView::showPassiveDropped(const QString &title) { if (!currentBasket()->isLocked()) { // TODO: Keep basket, so that we show the message only if something was added to a NOT visible basket m_passiveDroppedTitle = title; m_passiveDroppedSelection = currentBasket()->selectedNotes(); QTimer::singleShot(c_delayTooltipTime, this, SLOT(showPassiveDroppedDelayed())); // DELAY IT BELOW: } else showPassiveImpossible(i18n("No note was added.")); } void BNPView::showPassiveDroppedDelayed() { if (isMainWindowActive() || m_passiveDroppedSelection == 0) return; QString title = m_passiveDroppedTitle; QImage contentsImage = NoteDrag::feedbackPixmap(m_passiveDroppedSelection).toImage(); QResource::registerResource(contentsImage.bits(), ":/images/passivepopup_image"); if (Settings::useSystray()) { /*Uncomment after switching to QSystemTrayIcon or port to KStatusNotifierItem See also other occurrences of Global::systemTray below*/ /*KPassivePopup::message(KPassivePopup::Boxed, title.arg(Tools::textToHTMLWithoutP(currentBasket()->basketName())), (contentsImage.isNull() ? "" : ""), KIconLoader::global()->loadIcon( currentBasket()->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true ), Global::systemTray);*/ } else { KPassivePopup::message(KPassivePopup::Boxed, title.arg(Tools::textToHTMLWithoutP(currentBasket()->basketName())), (contentsImage.isNull() ? "" : ""), KIconLoader::global()->loadIcon(currentBasket()->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true), (QWidget *)this); } } void BNPView::showPassiveImpossible(const QString &message) { if (Settings::useSystray()) { /*KPassivePopup::message(KPassivePopup::Boxed, QString("%1") .arg(i18n("Basket %1 is locked")) .arg(Tools::textToHTMLWithoutP(currentBasket()->basketName())), message, KIconLoader::global()->loadIcon( currentBasket()->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true ), Global::systemTray);*/ } else { /*KPassivePopup::message(KPassivePopup::Boxed, QString("%1") .arg(i18n("Basket %1 is locked")) .arg(Tools::textToHTMLWithoutP(currentBasket()->basketName())), message, KIconLoader::global()->loadIcon( currentBasket()->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true ), (QWidget*)this);*/ } } void BNPView::showPassiveContentForced() { showPassiveContent(/*forceShow=*/true); } void BNPView::showPassiveContent(bool forceShow /* = false*/) { if (!forceShow && isMainWindowActive()) return; // FIXME: Duplicate code (2 times) QString message; if (Settings::useSystray()) { /*KPassivePopup::message(KPassivePopup::Boxed, "" + Tools::makeStandardCaption( currentBasket()->isLocked() ? QString("%1 %2") .arg(Tools::textToHTMLWithoutP(currentBasket()->basketName()), i18n("(Locked)")) : Tools::textToHTMLWithoutP(currentBasket()->basketName()) ), message, KIconLoader::global()->loadIcon( currentBasket()->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true ), Global::systemTray);*/ } else { KPassivePopup::message(KPassivePopup::Boxed, "" + Tools::makeStandardCaption(currentBasket()->isLocked() ? QString("%1 %2").arg(Tools::textToHTMLWithoutP(currentBasket()->basketName()), i18n("(Locked)")) : Tools::textToHTMLWithoutP(currentBasket()->basketName())), message, KIconLoader::global()->loadIcon(currentBasket()->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true), (QWidget *)this); } } void BNPView::showPassiveLoading(BasketScene *basket) { if (isMainWindowActive()) return; if (Settings::useSystray()) { /*KPassivePopup::message(KPassivePopup::Boxed, Tools::textToHTMLWithoutP(basket->basketName()), i18n("Loading..."), KIconLoader::global()->loadIcon( basket->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true ), Global::systemTray);*/ } else { KPassivePopup::message(KPassivePopup::Boxed, Tools::textToHTMLWithoutP(basket->basketName()), i18n("Loading..."), KIconLoader::global()->loadIcon(basket->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true), (QWidget *)this); } } void BNPView::addNoteText() { showMainWindow(); currentBasket()->insertEmptyNote(NoteType::Text); } void BNPView::addNoteHtml() { showMainWindow(); currentBasket()->insertEmptyNote(NoteType::Html); } void BNPView::addNoteImage() { showMainWindow(); currentBasket()->insertEmptyNote(NoteType::Image); } void BNPView::addNoteLink() { showMainWindow(); currentBasket()->insertEmptyNote(NoteType::Link); } void BNPView::addNoteCrossReference() { showMainWindow(); currentBasket()->insertEmptyNote(NoteType::CrossReference); } void BNPView::addNoteColor() { showMainWindow(); currentBasket()->insertEmptyNote(NoteType::Color); } void BNPView::aboutToHideNewBasketPopup() { QTimer::singleShot(0, this, SLOT(cancelNewBasketPopup())); } void BNPView::cancelNewBasketPopup() { m_newBasketPopup = false; } void BNPView::setNewBasketPopup() { m_newBasketPopup = true; } void BNPView::setWindowTitle(QString s) { emit setWindowCaption(s); } void BNPView::updateStatusBarHint() { m_statusbar->updateStatusBarHint(); } void BNPView::setSelectionStatus(QString s) { m_statusbar->setSelectionStatus(s); } void BNPView::setLockStatus(bool isLocked) { m_statusbar->setLockStatus(isLocked); } void BNPView::postStatusbarMessage(const QString &msg) { m_statusbar->postStatusbarMessage(msg); } void BNPView::setStatusBarHint(const QString &hint) { m_statusbar->setStatusBarHint(hint); } void BNPView::setUnsavedStatus(bool isUnsaved) { m_statusbar->setUnsavedStatus(isUnsaved); } void BNPView::setActive(bool active) { KMainWindow *win = Global::activeMainWindow(); if (!win) return; if (active == isMainWindowActive()) return; // qApp->updateUserTimestamp(); // If "activate on mouse hovering systray", or "on drag through systray" Global::systemTray->activate(); } void BNPView::hideOnEscape() { if (Settings::useSystray()) setActive(false); } bool BNPView::isPart() { return objectName() == "BNPViewPart"; } bool BNPView::isMainWindowActive() { KMainWindow *main = Global::activeMainWindow(); if (main && main->isActiveWindow()) return true; return false; } void BNPView::newBasket() { askNewBasket(); } bool BNPView::createNoteHtml(const QString content, const QString basket) { BasketScene *b = basketForFolderName(basket); if (!b) return false; Note *note = NoteFactory::createNoteHtml(content, b); if (!note) return false; b->insertCreatedNote(note); return true; } bool BNPView::changeNoteHtml(const QString content, const QString basket, const QString noteName) { BasketScene *b = basketForFolderName(basket); if (!b) return false; Note *note = noteForFileName(noteName, *b); if (!note || note->content()->type() != NoteType::Html) return false; HtmlContent *noteContent = (HtmlContent *)note->content(); noteContent->setHtml(content); note->saveAgain(); return true; } bool BNPView::createNoteFromFile(const QString url, const QString basket) { BasketScene *b = basketForFolderName(basket); if (!b) return false; QUrl kurl(url); if (url.isEmpty()) return false; Note *n = NoteFactory::copyFileAndLoad(kurl, b); if (!n) return false; b->insertCreatedNote(n); return true; } QStringList BNPView::listBaskets() { QStringList basketList; QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); basketList.append(item->basket()->basketName()); basketList.append(item->basket()->folderName()); ++it; } return basketList; } void BNPView::handleCommandLine() { QCommandLineParser *parser = Global::commandLineOpts; /* Custom data folder */ QString customDataFolder = parser->value("data-folder"); if (!customDataFolder.isNull() && !customDataFolder.isEmpty()) { Global::setCustomSavesFolder(customDataFolder); } /* Debug window */ if (parser->isSet("debug")) { new DebugWindow(); Global::debugWindow->show(); } /* Crash Handler to Mail Developers when Crashing: */ #ifndef BASKET_USE_DRKONQI if (!parser->isSet("use-drkonqi")) KCrash::setCrashHandler(Crash::crashHandler); #endif } void BNPView::reloadBasket(const QString &folderName) { basketForFolderName(folderName)->reload(); } /** Scenario of "Hide main window to system tray icon when mouse move out of the window" : * - At enterEvent() we stop m_tryHideTimer * - After that and before next, we are SURE cursor is hovering window * - At leaveEvent() we restart m_tryHideTimer * - Every 'x' ms, timeoutTryHide() seek if cursor hover a widget of the application or not * - If yes, we musn't hide the window * - But if not, we start m_hideTimer to hide main window after a configured elapsed time * - timeoutTryHide() continue to be called and if cursor move again to one widget of the app, m_hideTimer is stopped * - If after the configured time cursor hasn't go back to a widget of the application, timeoutHide() is called * - It then hide the main window to systray icon * - When the user will show it, enterEvent() will be called the first time he enter mouse to it * - ... */ /** Why do as this ? Problems with the use of only enterEvent() and leaveEvent() : * - Resize window or hover titlebar isn't possible : leave/enterEvent * are * > Use the grip or Alt+rightDND to resize window * > Use Alt+DND to move window * - Each menu trigger the leavEvent */ void BNPView::enterEvent(QEvent *) { if (m_tryHideTimer) m_tryHideTimer->stop(); if (m_hideTimer) m_hideTimer->stop(); } void BNPView::leaveEvent(QEvent *) { if (Settings::useSystray() && Settings::hideOnMouseOut() && m_tryHideTimer) m_tryHideTimer->start(50); } void BNPView::timeoutTryHide() { // If a menu is displayed, do nothing for the moment if (qApp->activePopupWidget() != 0L) return; if (qApp->widgetAt(QCursor::pos()) != 0L) m_hideTimer->stop(); else if (!m_hideTimer->isActive()) { // Start only one time m_hideTimer->setSingleShot(true); m_hideTimer->start(Settings::timeToHideOnMouseOut() * 100); } // If a subdialog is opened, we mustn't hide the main window: if (qApp->activeWindow() != 0L && qApp->activeWindow() != Global::activeMainWindow()) m_hideTimer->stop(); } void BNPView::timeoutHide() { // We check that because the setting can have been set to off if (Settings::useSystray() && Settings::hideOnMouseOut()) setActive(false); m_tryHideTimer->stop(); } void BNPView::changedSelectedNotes() { // tabChanged(0); // FIXME: NOT OPTIMIZED } /*void BNPView::areSelectedNotesCheckedChanged(bool checked) { m_actCheckNotes->setChecked(checked && currentBasket()->showCheckBoxes()); }*/ void BNPView::enableActions() { BasketScene *basket = currentBasket(); if (!basket) return; if (m_actLockBasket) m_actLockBasket->setEnabled(!basket->isLocked() && basket->isEncrypted()); if (m_actPassBasket) m_actPassBasket->setEnabled(!basket->isLocked()); m_actPropBasket->setEnabled(!basket->isLocked()); m_actDelBasket->setEnabled(!basket->isLocked()); m_actExportToHtml->setEnabled(!basket->isLocked()); m_actShowFilter->setEnabled(!basket->isLocked()); m_actFilterAllBaskets->setEnabled(!basket->isLocked()); m_actResetFilter->setEnabled(!basket->isLocked()); basket->decoration()->filterBar()->setEnabled(!basket->isLocked()); } void BNPView::showMainWindow() { if (m_HiddenMainWindow) { m_HiddenMainWindow->show(); m_HiddenMainWindow = NULL; } else { KMainWindow *win = Global::activeMainWindow(); if (win) { win->show(); } } setActive(true); emit showPart(); } void BNPView::populateTagsMenu() { QMenu *menu = (QMenu *)(popupMenu("tags")); if (menu == 0 || currentBasket() == 0) // TODO: Display a messagebox. [menu is 0, surely because on first launch, the XMLGUI does not work!] return; menu->clear(); Note *referenceNote; if (currentBasket()->focusedNote() && currentBasket()->focusedNote()->isSelected()) referenceNote = currentBasket()->focusedNote(); else referenceNote = currentBasket()->firstSelected(); populateTagsMenu(*menu, referenceNote); m_lastOpenedTagsMenu = menu; // connect( menu, SIGNAL(aboutToHide()), this, SLOT(disconnectTagsMenu()) ); } void BNPView::populateTagsMenu(QMenu &menu, Note *referenceNote) { if (currentBasket() == 0) return; currentBasket()->m_tagPopupNote = referenceNote; bool enable = currentBasket()->countSelecteds() > 0; QList::iterator it; Tag *currentTag; State *currentState; int i = 10; for (it = Tag::all.begin(); it != Tag::all.end(); ++it) { // Current tag and first state of it: currentTag = *it; currentState = currentTag->states().first(); QKeySequence sequence; if (!currentTag->shortcut().isEmpty()) sequence = currentTag->shortcut(); StateAction *mi = new StateAction(currentState, QKeySequence(sequence), this, true); // The previously set ID will be set in the actions themselves as data. mi->setData(i); if (referenceNote && referenceNote->hasTag(currentTag)) mi->setChecked(true); menu.addAction(mi); if (!currentTag->shortcut().isEmpty()) m_actionCollection->setDefaultShortcut(mi, sequence); mi->setEnabled(enable); ++i; } menu.addSeparator(); // I don't like how this is implemented; but I can't think of a better way // to do this, so I will have to leave it for now QAction *act = new QAction(i18n("&Assign new Tag..."), &menu); act->setData(1); act->setEnabled(enable); menu.addAction(act); act = new QAction(QIcon::fromTheme("edit-delete"), i18n("&Remove All"), &menu); act->setData(2); if (!currentBasket()->selectedNotesHaveTags()) act->setEnabled(false); menu.addAction(act); act = new QAction(QIcon::fromTheme("configure"), i18n("&Customize..."), &menu); act->setData(3); menu.addAction(act); connect(&menu, SIGNAL(triggered(QAction *)), currentBasket(), SLOT(toggledTagInMenu(QAction *))); connect(&menu, SIGNAL(aboutToHide()), currentBasket(), SLOT(unlockHovering())); connect(&menu, SIGNAL(aboutToHide()), currentBasket(), SLOT(disableNextClick())); } void BNPView::connectTagsMenu() { connect(popupMenu("tags"), SIGNAL(aboutToShow()), this, SLOT(populateTagsMenu())); connect(popupMenu("tags"), SIGNAL(aboutToHide()), this, SLOT(disconnectTagsMenu())); } /* * The Tags menu is ONLY created once the BasKet KPart is first shown. * So we can use this menu only from then? * When the KPart is changed in Kontact, and then the BasKet KPart is shown again, * Kontact created a NEW Tags menu. So we should connect again. * But when Kontact main window is hidden and then re-shown, the menu does not change. * So we disconnect at hide event to ensure only one connection: the next show event will not connects another time. */ void BNPView::showEvent(QShowEvent *) { if (isPart()) QTimer::singleShot(0, this, SLOT(connectTagsMenu())); if (m_firstShow) { m_firstShow = false; onFirstShow(); } if (isPart() /*TODO: && !LikeBack::enabledBar()*/) { Global::likeBack->enableBar(); } } void BNPView::hideEvent(QHideEvent *) { if (isPart()) { disconnect(popupMenu("tags"), SIGNAL(aboutToShow()), this, SLOT(populateTagsMenu())); disconnect(popupMenu("tags"), SIGNAL(aboutToHide()), this, SLOT(disconnectTagsMenu())); } if (isPart()) Global::likeBack->disableBar(); } void BNPView::disconnectTagsMenu() { QTimer::singleShot(0, this, SLOT(disconnectTagsMenuDelayed())); } void BNPView::disconnectTagsMenuDelayed() { disconnect(m_lastOpenedTagsMenu, SIGNAL(triggered(QAction *)), currentBasket(), SLOT(toggledTagInMenu(QAction *))); disconnect(m_lastOpenedTagsMenu, SIGNAL(aboutToHide()), currentBasket(), SLOT(unlockHovering())); disconnect(m_lastOpenedTagsMenu, SIGNAL(aboutToHide()), currentBasket(), SLOT(disableNextClick())); } void BNPView::loadCrossReference(QString link) { // remove "basket://" and any encoding. QString folderName = link.mid(9, link.length() - 9); folderName = QUrl::fromPercentEncoding(folderName.toUtf8()); BasketScene *basket = this->basketForFolderName(folderName); if (!basket) return; this->setCurrentBasketInHistory(basket); } QString BNPView::folderFromBasketNameLink(QStringList pages, QTreeWidgetItem *parent) { QString found = ""; QString page = pages.first(); page = QUrl::fromPercentEncoding(page.toUtf8()); pages.removeFirst(); if (page == "..") { QTreeWidgetItem *p; if (parent) p = parent->parent(); else p = m_tree->currentItem()->parent(); found = this->folderFromBasketNameLink(pages, p); } else if (!parent && page.isEmpty()) { parent = m_tree->invisibleRootItem(); found = this->folderFromBasketNameLink(pages, parent); } else { if (!parent && (page == "." || !page.isEmpty())) { parent = m_tree->currentItem(); } QRegExp re(":\\{([0-9]+)\\}"); re.setMinimal(true); int pos = 0; pos = re.indexIn(page, pos); int basketNum = 1; if (pos != -1) basketNum = re.cap(1).toInt(); page = page.left(page.length() - re.matchedLength()); for (int i = 0; i < parent->childCount(); i++) { QTreeWidgetItem *child = parent->child(i); if (child->text(0).toLower() == page.toLower()) { basketNum--; if (basketNum == 0) { if (pages.count() > 0) { found = this->folderFromBasketNameLink(pages, child); break; } else { found = ((BasketListViewItem *)child)->basket()->folderName(); break; } } } else found = ""; } } return found; } void BNPView::sortChildrenAsc() { m_tree->currentItem()->sortChildren(0, Qt::AscendingOrder); } void BNPView::sortChildrenDesc() { m_tree->currentItem()->sortChildren(0, Qt::DescendingOrder); } void BNPView::sortSiblingsAsc() { QTreeWidgetItem *parent = m_tree->currentItem()->parent(); if (!parent) m_tree->sortItems(0, Qt::AscendingOrder); else parent->sortChildren(0, Qt::AscendingOrder); } void BNPView::sortSiblingsDesc() { QTreeWidgetItem *parent = m_tree->currentItem()->parent(); if (!parent) m_tree->sortItems(0, Qt::DescendingOrder); else parent->sortChildren(0, Qt::DescendingOrder); } diff --git a/src/bnpview.h b/src/bnpview.h index 07fc5e7..73f6b89 100644 --- a/src/bnpview.h +++ b/src/bnpview.h @@ -1,397 +1,383 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef BNPVIEW_H #define BNPVIEW_H #include #include #include #include #include #include "basket_export.h" #include "global.h" class QDomElement; class QStackedWidget; class QPixmap; class QTimer; class QTreeWidget; class QTreeWidgetItem; class QUndoStack; class QEvent; class QHideEvent; class QShowEvent; class QAction; class KToggleAction; class QMenu; class KTar; class DesktopColorPicker; class RegionGrabber; class BasketScene; class DecoratedBasket; class BasketListView; class BasketListViewItem; class BasketTreeListView; class NoteSelection; class BasketStatusBar; class Tag; class State; class Note; class KMainWindow; class BASKET_EXPORT BNPView : public QSplitter { Q_OBJECT Q_CLASSINFO("D Bus Interface", "org.kde.basket.dbus"); public: /// CONSTRUCTOR AND DESTRUCTOR: BNPView(QWidget *parent, const char *name, KXMLGUIClient *aGUIClient, KActionCollection *actionCollection, BasketStatusBar *bar); ~BNPView() override; /// MANAGE CONFIGURATION EVENTS!: void setTreePlacement(bool onLeft); void relayoutAllBaskets(); void recomputeAllStyles(); void removedStates(const QList &deletedStates); void linkLookChanged(); void filterPlacementChanged(bool onTop); /// MANAGE BASKETS: BasketListViewItem *listViewItemForBasket(BasketScene *basket); BasketScene *currentBasket(); BasketScene *parentBasketOf(BasketScene *basket); void setCurrentBasket(BasketScene *basket); void setCurrentBasketInHistory(BasketScene *basket); void removeBasket(BasketScene *basket); /// For NewBasketDialog (and later some other classes): int topLevelItemCount(); /// BasketListViewItem *topLevelItem(int i); int basketCount(QTreeWidgetItem *parent = 0); bool canFold(); bool canExpand(); void enableActions(); private: //! Create element with void writeBasketElement(QTreeWidgetItem *item, QXmlStreamWriter &steam); public slots: void countsChanged(BasketScene *basket); void notesStateChanged(); bool convertTexts(); void updateBasketListViewItem(BasketScene *basket); void save(); void save(QTreeWidget *listView, QTreeWidgetItem *firstItem, QXmlStreamWriter &stream); void saveSubHierarchy(QTreeWidgetItem *item, QXmlStreamWriter &stream, bool recursive); void load(); void load(QTreeWidgetItem *item, const QDomElement &baskets); void loadNewBasket(const QString &folderName, const QDomElement &properties, BasketScene *parent); void goToPreviousBasket(); void goToNextBasket(); void foldBasket(); void expandBasket(); void closeAllEditors(); /// void toggleFilterAllBaskets(bool doFilter); void newFilter(); void newFilterFromFilterBar(); bool isFilteringAllBaskets(); // From main window void importKNotes(); void importKJots(); void importKnowIt(); void importTuxCards(); void importStickyNotes(); void importTomboy(); void importJreepadFile(); void importTextFile(); void backupRestore(); void checkCleanup(); /** Note */ void activatedTagShortcut(); void exportToHTML(); void editNote(); void cutNote(); void copyNote(); void delNote(); void openNote(); void openNoteWith(); void saveNoteAs(); void noteGroup(); void noteUngroup(); void moveOnTop(); void moveOnBottom(); void moveNoteUp(); void moveNoteDown(); void slotSelectAll(); void slotUnselectAll(); void slotInvertSelection(); void slotResetFilter(); void slotColorFromScreen(bool global = false); void slotColorFromScreenGlobal(); void colorPicked(const QColor &color); void colorPickingCanceled(); void slotConvertTexts(); /** Global shortcuts */ void addNoteText(); void addNoteHtml(); void addNoteImage(); void addNoteLink(); void addNoteCrossReference(); void addNoteColor(); /** Passive Popups for Global Actions */ void showPassiveDropped(const QString &title); void showPassiveDroppedDelayed(); // Do showPassiveDropped(), but delayed void showPassiveContent(bool forceShow = false); void showPassiveContentForced(); void showPassiveImpossible(const QString &message); void showPassiveLoading(BasketScene *basket); // For GUI : void setFiltering(bool filtering); /** Edit */ void undo(); void redo(); void globalPasteInCurrentBasket(); void pasteInCurrentBasket(); void pasteSelInCurrentBasket(); void pasteToBasket(int index, QClipboard::Mode mode = QClipboard::Clipboard); void showHideFilterBar(bool show, bool switchFocus = true); /** Insert **/ void insertEmpty(int type); void insertWizard(int type); void grabScreenshot(bool global = false); void grabScreenshotGlobal(); void screenshotGrabbed(const QPixmap &pixmap); /** BasketScene */ void askNewBasket(); void askNewBasket(BasketScene *parent, BasketScene *pickProperties); void askNewSubBasket(); void askNewSiblingBasket(); void aboutToHideNewBasketPopup(); void setNewBasketPopup(); void cancelNewBasketPopup(); void propBasket(); void delBasket(); void doBasketDeletion(BasketScene *basket); void password(); void saveAsArchive(); void openArchive(); void delayedOpenArchive(); void delayedOpenBasket(); void lockBasket(); void hideOnEscape(); void changedSelectedNotes(); void timeoutTryHide(); void timeoutHide(); void loadCrossReference(QString link); QString folderFromBasketNameLink(QStringList pages, QTreeWidgetItem *parent = 0); void sortChildrenAsc(); void sortChildrenDesc(); void sortSiblingsAsc(); void sortSiblingsDesc(); public: static QString s_fileToOpen; static QString s_basketToOpen; public slots: void addWelcomeBaskets(); private slots: void updateNotesActions(); void slotBasketChanged(); void canUndoRedoChanged(); void currentBasketChanged(); void isLockedChanged(); void lateInit(); void onFirstShow(); public: QAction *m_actEditNote; QAction *m_actOpenNote; QAction *m_actPaste; QAction *m_actGrabScreenshot; QAction *m_actColorPicker; QAction *m_actLockBasket; QAction *m_actPassBasket; QAction *actNewBasket; QAction *actNewSubBasket; QAction *actNewSiblingBasket; QAction *m_actHideWindow; QAction *m_actExportToHtml; QAction *m_actPropBasket; QAction *m_actSortChildrenAsc; QAction *m_actSortChildrenDesc; QAction *m_actSortSiblingsAsc; QAction *m_actSortSiblingsDesc; QAction *m_actDelBasket; KToggleAction *m_actFilterAllBaskets; private: // Basket actions: QAction *m_actSaveAsArchive; QAction *m_actOpenArchive; // Notes actions : QAction *m_actOpenNoteWith; QAction *m_actSaveNoteAs; QAction *m_actGroup; QAction *m_actUngroup; QAction *m_actMoveOnTop; QAction *m_actMoveNoteUp; QAction *m_actMoveNoteDown; QAction *m_actMoveOnBottom; // Edit actions : QAction *m_actUndo; QAction *m_actRedo; QAction *m_actCutNote; QAction *m_actCopyNote; QAction *m_actDelNote; QAction *m_actSelectAll; QAction *m_actUnselectAll; QAction *m_actInvertSelection; // Insert actions : // QAction *m_actInsertText; QAction *m_actInsertHtml; QAction *m_actInsertLink; QAction *m_actInsertCrossReference; QAction *m_actInsertImage; QAction *m_actInsertColor; QAction *m_actImportKMenu; QAction *m_actInsertLauncher; QAction *m_actImportIcon; QAction *m_actLoadFile; QList m_insertActions; // Basket actions : KToggleAction *m_actShowFilter; QAction *m_actResetFilter; // Go actions : QAction *m_actPreviousBasket; QAction *m_actNextBasket; QAction *m_actFoldBasket; QAction *m_actExpandBasket; // QAction *m_convertTexts; // FOR_BETA_PURPOSE void setupActions(); void setupGlobalShortcuts(); DecoratedBasket *currentDecoratedBasket(); public: BasketScene *loadBasket(const QString &folderName); // Public only for class Archive BasketListViewItem *appendBasket(BasketScene *basket, QTreeWidgetItem *parentItem); // Public only for class Archive BasketScene *basketForFolderName(const QString &folderName); Note *noteForFileName(const QString &fileName, BasketScene &basket, Note *note = 0); QMenu *popupMenu(const QString &menuName); bool isPart(); bool isMainWindowActive(); void showMainWindow(); // TODO: dcop calls -- dbus these public Q_SLOTS: Q_SCRIPTABLE void newBasket(); Q_SCRIPTABLE void handleCommandLine(); Q_SCRIPTABLE void reloadBasket(const QString &folderName); Q_SCRIPTABLE bool createNoteHtml(const QString content, const QString basket); Q_SCRIPTABLE QStringList listBaskets(); Q_SCRIPTABLE bool createNoteFromFile(const QString url, const QString basket); Q_SCRIPTABLE bool changeNoteHtml(const QString content, const QString basket, const QString noteName); public slots: void setWindowTitle(QString s); void updateStatusBarHint(); void setSelectionStatus(QString s); void setLockStatus(bool isLocked); void postStatusbarMessage(const QString &); void setStatusBarHint(const QString &); void setUnsavedStatus(bool isUnsaved); void setActive(bool active = true); KActionCollection *actionCollection() { return m_actionCollection; }; void populateTagsMenu(); void populateTagsMenu(QMenu &menu, Note *referenceNote); void connectTagsMenu(); void disconnectTagsMenu(); void disconnectTagsMenuDelayed(); protected: void showEvent(QShowEvent *) override; void hideEvent(QHideEvent *) override; private: QMenu *m_lastOpenedTagsMenu; private slots: void slotPressed(QTreeWidgetItem *item, int column); void needSave(QTreeWidgetItem *); void slotContextMenu(const QPoint &pos); void slotShowProperties(QTreeWidgetItem *item); void initialize(); signals: void basketChanged(); void setWindowCaption(const QString &s); void showPart(); protected: void enterEvent(QEvent *) override; void leaveEvent(QEvent *) override; protected: void hideMainWindow(); private: BasketTreeListView *m_tree; QStackedWidget *m_stack; bool m_loading; bool m_newBasketPopup; bool m_firstShow; DesktopColorPicker *m_colorPicker; bool m_colorPickWasShown; bool m_colorPickWasGlobal; RegionGrabber *m_regionGrabber; QString m_passiveDroppedTitle; NoteSelection *m_passiveDroppedSelection; static const int c_delayTooltipTime; KActionCollection *m_actionCollection; KXMLGUIClient *m_guiClient; BasketStatusBar *m_statusbar; QTimer *m_tryHideTimer; QTimer *m_hideTimer; QUndoStack *m_history; KMainWindow *m_HiddenMainWindow; }; #endif // BNPVIEW_H diff --git a/src/colorpicker.cpp b/src/colorpicker.cpp index 9aaf1c1..74416a7 100644 --- a/src/colorpicker.cpp +++ b/src/colorpicker.cpp @@ -1,102 +1,88 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "colorpicker.h" #include #include #include #include #include /// /// /** DektopColorPicker */ /* From Qt documentation: * " Note that only visible widgets can grab mouse input. * If isVisible() returns FALSE for a widget, that widget cannot call grabMouse(). " * So, we should use an always visible widget to be able to pick a color from screen, * even by first the main window (user rarely want to grab a color from BasKet!) * or use a global shortcut (main window can be hidden when hitting that shortcut). */ DesktopColorPicker::DesktopColorPicker() : QDesktopWidget() { setObjectName("DesktopColorPicker"); m_gettingColorFromScreen = false; } DesktopColorPicker::~DesktopColorPicker() { } void DesktopColorPicker::pickColor() { m_gettingColorFromScreen = true; // Global::mainContainer->setActive(false); QTimer::singleShot(50, this, SLOT(slotDelayedPick())); } /* When firered from basket context menu, and not from menu, grabMouse doesn't work! * It's perhapse because context menu call slotColorFromScreen() and then * ungrab the mouse (since menus grab the mouse). * But why isn't there such bug with normal menus?... * By calling this method with a QTimer::singleShot, we are sure context menu code is * finished and we can grab the mouse without loosing the grab: */ void DesktopColorPicker::slotDelayedPick() { grabKeyboard(); grabMouse(Qt::CrossCursor); } /* Validate the color */ void DesktopColorPicker::mouseReleaseEvent(QMouseEvent *event) { if (m_gettingColorFromScreen) { m_gettingColorFromScreen = false; releaseMouse(); releaseKeyboard(); // Grab color of pixel QPoint p = event->globalPos(); QPixmap pixmap = QPixmap::grabWindow(QApplication::desktop()->winId(), p.x(), p.y(), 1, 1); QColor color(pixmap.toImage().pixel(0, 0)); emit pickedColor(color); } else QDesktopWidget::mouseReleaseEvent(event); } /* Cancel the mode */ void DesktopColorPicker::keyPressEvent(QKeyEvent *event) { if (m_gettingColorFromScreen) if (event->key() == Qt::Key_Escape) { m_gettingColorFromScreen = false; releaseMouse(); releaseKeyboard(); emit canceledPick(); } QDesktopWidget::keyPressEvent(event); } diff --git a/src/colorpicker.h b/src/colorpicker.h index 169c8c4..fdf1d2d 100644 --- a/src/colorpicker.h +++ b/src/colorpicker.h @@ -1,60 +1,46 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef COLORPICKER_H #define COLORPICKER_H #include class QKeyEvent; class QMouseEvent; /** Class to pick a color on the screen * @author Sébastien Laoût */ class DesktopColorPicker : public QDesktopWidget { Q_OBJECT public: /** Constructor, initializer and destructor */ DesktopColorPicker(); ~DesktopColorPicker() override; public slots: /** Begin color picking. * This function returns immediately, and pickedColor() is emitted if user has * chosen a color, and not canceled the process (by pressing Escape). */ void pickColor(); signals: /** When user picked a color, this signal is emitted. */ void pickedColor(const QColor &color); /** When user cancel a picking (by pressing Escape), this signal is emitted. */ void canceledPick(); protected slots: void slotDelayedPick(); protected: void mouseReleaseEvent(QMouseEvent *event) override; void keyPressEvent(QKeyEvent *event) override; bool m_gettingColorFromScreen; }; #endif // COLORPICKER_H diff --git a/src/crashhandler.cpp b/src/crashhandler.cpp index ab4f7d5..77c5b66 100644 --- a/src/crashhandler.cpp +++ b/src/crashhandler.cpp @@ -1,246 +1,239 @@ -// Code from Amarok. - -/*************************************************************************** - * Copyright (C) 2005 Max Howell * - * * - * 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. * - * * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2005 Max Howell + * SPDX-License-Identifier: LGPL-2.1-or-later + */ #include "crashhandler.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #endif #include //popen, fread #include //pid_t #include //waitpid #include //write, getppid static QString runCommand(const QByteArray &command) { static const uint SIZE = 40960; // 40 KiB static char stdoutBuf[SIZE]; // debug() << "Running: " << command << endl; FILE *process = ::popen(command, "r"); stdoutBuf[std::fread(static_cast(stdoutBuf), sizeof(char), SIZE - 1, process)] = '\0'; ::pclose(process); return QString::fromLocal8Bit(stdoutBuf); } void Crash::crashHandler(int /*signal*/) { #ifndef Q_OS_WIN // we need to fork to be able to get a // semi-decent bt - I dunno why const pid_t pid = ::fork(); if (pid <= 0) { // we are the child process (the result of the fork) // debug() << "amaroK is crashing...\n"; QString subject = "[basket-crash] " VERSION " "; QString body = i18n( "%1 has crashed! We're sorry about this.\n" "\n" "But, all is not lost! You could potentially help us fix the crash. " "Information describing the crash is below, so just click send, " "or if you have time, write a brief description of how the crash happened first.\n\n" "Many thanks.", QGuiApplication::applicationDisplayName()) + "\n\n"; body += "\n\n\n\n\n\n" + i18n("The information below is to help the developers identify the problem, " "please do not modify it.") + "\n\n\n\n"; body += "======== DEBUG INFORMATION =======\n" "Version: " VERSION "\n" "CC version: " __VERSION__ "\n" // assuming we're using GCC "Frameworks: " KCOREADDONS_VERSION_STRING "\n"; // "TagLib: %2.%3.%4\n"; /* body = body .arg( TAGLIB_MAJOR_VERSION ) .arg( TAGLIB_MINOR_VERSION ) .arg( TAGLIB_PATCH_VERSION );*/ #ifdef NDEBUG body += "NDEBUG: true"; #endif body += '\n'; body += "OS:\n" + Crash::getOSVersionInfo() + '\n'; /// obtain the backtrace with gdb QTemporaryFile temp; temp.open(); temp.setAutoRemove(true); const int handle = temp.handle(); // QCString gdb_command_string = // "file amaroqApp\n" // "attach " + QCString().setNum( ::getppid() ) + "\n" // "bt\n" "echo \\n\n" // "thread apply all bt\n"; const QByteArray gdb_batch = "bt\n" "echo \\n\\n\n" "bt full\n" "echo \\n\\n\n" "echo ==== (gdb) thread apply all bt ====\\n\n" "thread apply all bt\n"; ::write(handle, gdb_batch, gdb_batch.length()); ::fsync(handle); // so we can read stderr too ::dup2(fileno(stdout), fileno(stderr)); QByteArray gdb; gdb = "gdb --nw -n --batch -x "; gdb += temp.fileName().toLatin1(); gdb += ' '; gdb += QGuiApplication::applicationFilePath(); gdb += ' '; gdb += QByteArray().setNum(::getppid()); QString bt = runCommand(gdb); /// clean up bt.remove("(no debugging symbols found)..."); bt.remove("(no debugging symbols found)\n"); bt.replace(QRegExp("\n{2,}"), "\n"); // clean up multiple \n characters bt.trimmed(); /// analyze usefulness bool useful = true; const QString fileCommandOutput = runCommand("file `which basket`"); if (fileCommandOutput.indexOf("not stripped") == -1) subject += "[___stripped]"; // same length as below else subject += "[NOTstripped]"; if (!bt.isEmpty()) { const int invalidFrames = bt.count(QRegExp("\n#[0-9]+\\s+0x[0-9A-Fa-f]+ in \\?\\?")); const int validFrames = bt.count(QRegExp("\n#[0-9]+\\s+0x[0-9A-Fa-f]+ in [^?]")); const int totalFrames = invalidFrames + validFrames; if (totalFrames > 0) { const double validity = double(validFrames) / totalFrames; subject += QString("[validity: %1]").arg(validity, 0, 'f', 2); if (validity <= 0.5) useful = false; } subject += QString("[frames: %1]").arg(totalFrames, 3 /*padding*/); if (bt.indexOf(QRegExp(" at \\w*\\.cpp:\\d+\n")) != -1) subject += "[line numbers]"; } else useful = false; // subject += QString("[%1]").arg( AmarokConfig::soundSystem().remove( QRegExp("-?engine") ) ); // debug() << subject << endl; // TODO -fomit-frame-pointer buggers up the backtrace, so detect it // TODO -O optimization can rearrange execution and stuff so show a warning for the developer // TODO pass the CXXFLAGS used with the email if (useful) { body += "==== file `which basket` ==========\n"; body += fileCommandOutput + '\n'; body += "==== (gdb) bt =====================\n"; body += bt; //+ "\n\n"; // body += "==== kBacktrace() ================\n"; // body += kBacktrace(); // TODO startup notification KToolInvocation::invokeMailer( /*to*/ "kde.basket@gmx.com", /*cc*/ QString(), /*bcc*/ QString(), /*subject*/ subject, /*body*/ body, /*messageFile*/ QString(), /*attachURLs*/ QStringList(), /*startup_id*/ ""); } else { qDebug() << '\n' + i18n("%1 has crashed! We're sorry about this.\n\n" "But, all is not lost! Perhaps an upgrade is already available " "which fixes the problem. Please check your distribution's software repository.", QGuiApplication::applicationDisplayName()); } //_exit() exits immediately, otherwise this // function is called repeatedly ad finitum ::_exit(255); } else { // we are the process that crashed ::alarm(0); // wait for child to exit ::waitpid(pid, NULL, 0); ::_exit(253); } #endif //#ifndef Q_OS_WIN } QString Crash::getOSVersionInfo() { QString result; #ifdef Q_OS_UNIX QProcess process; process.start("lsb_release", QStringList("-a")); if (process.waitForFinished()) { result += "lsb_release -a:\n"; result += QString(process.readAll()); } // Read version files in /etc QStringList versionFileMasks = QStringList() << "*release" << "*version"; QFileInfoList versionFiles = QDir("/etc").entryInfoList(versionFileMasks, QDir::Files); foreach (const QFileInfo &versionFile, versionFiles) { result += versionFile.absoluteFilePath() + ":\n"; QFile file(versionFile.absoluteFilePath()); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&file); result += stream.readAll(); } } #else result = QString("Windows WinVersion=0x%1").arg((int)QSysInfo::windowsVersion(), 0, 16); #endif return result; } diff --git a/src/crashhandler.h b/src/crashhandler.h index 34350f3..2cfc91c 100644 --- a/src/crashhandler.h +++ b/src/crashhandler.h @@ -1,32 +1,25 @@ -// Code from Amarok. - -/*************************************************************************** - * Copyright (C) 2005 Max Howell * - * * - * 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. * - * * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2005 Max Howell + * SPDX-License-Identifier: LGPL-2.1-or-later + */ #ifndef CRASH_H #define CRASH_H #include /** * @author Max Howell * @short The amaroK crash-handler * * I'm not entirely sure why this had to be inside a class, but it * wouldn't work otherwise *shrug* */ class Crash { public: static void crashHandler(int signal); static QString getOSVersionInfo(); }; #endif diff --git a/src/debugwindow.cpp b/src/debugwindow.cpp index 22d8059..6abc5ca 100644 --- a/src/debugwindow.cpp +++ b/src/debugwindow.cpp @@ -1,75 +1,61 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "debugwindow.h" #include #include #include #include #include #include #include "global.h" DebugWindow::DebugWindow(QWidget *parent) : QWidget(parent) { Global::debugWindow = this; setWindowTitle(i18n("Debug Window")); layout = new QVBoxLayout(this); textBrowser = new QTextBrowser(this); textBrowser->setWordWrapMode(QTextOption::NoWrap); layout->addWidget(textBrowser); textBrowser->show(); } DebugWindow::~DebugWindow() { delete textBrowser; delete layout; } void DebugWindow::postMessage(const QString msg) { textBrowser->append(msg); } DebugWindow &DebugWindow::operator<<(const QString msg) { // This can be used from a different thread QMetaObject::invokeMethod(this, "postMessage", Qt::QueuedConnection, Q_ARG(QString, msg)); return *this; } void DebugWindow::insertHLine() { textBrowser->append("
"); } void DebugWindow::closeEvent(QCloseEvent *event) { Global::debugWindow = 0L; QWidget::closeEvent(event); } diff --git a/src/debugwindow.h b/src/debugwindow.h index 4df6064..431cbde 100644 --- a/src/debugwindow.h +++ b/src/debugwindow.h @@ -1,58 +1,44 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef DEBUGWINDOW_H #define DEBUGWINDOW_H #include class QVBoxLayout; class QTextBrowser; class QString; class QCloseEvent; /**A simple window that display text through debugging messages. *@author Sébastien Laoût */ class DebugWindow : public QWidget { Q_OBJECT public: /** Constructor and destructor */ explicit DebugWindow(QWidget *parent = nullptr); ~DebugWindow() override; /** Methods to post a message to the debug window */ Q_INVOKABLE void postMessage(const QString msg); DebugWindow &operator<<(const QString msg); void insertHLine(); protected: void closeEvent(QCloseEvent *event) override; private: QVBoxLayout *layout; QTextBrowser *textBrowser; }; #define DEBUG_WIN \ if (Global::debugWindow) \ *Global::debugWindow #endif // DEBUGWINDOW_H diff --git a/src/decoratedbasket.cpp b/src/decoratedbasket.cpp index c9fd09a..0b2eb8a 100644 --- a/src/decoratedbasket.cpp +++ b/src/decoratedbasket.cpp @@ -1,96 +1,82 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "decoratedbasket.h" #include #include #include "basketscene.h" #include "filter.h" #include "settings.h" /** Class DecoratedBasket: */ DecoratedBasket::DecoratedBasket(QWidget *parent, const QString &folderName, Qt::WindowFlags fl) : QWidget(parent, fl) { m_layout = new QVBoxLayout(this); m_filter = new FilterBar(this); m_basket = new BasketScene(this, folderName); m_basket->graphicsView()->setParent(this); m_layout->addWidget(m_basket->graphicsView()); setFilterBarPosition(Settings::filterOnTop()); m_filter->hide(); m_basket->setFocus(); // To avoid the filter bar have focus on load connect(m_filter, SIGNAL(newFilter(const FilterData &)), m_basket, SLOT(newFilter(const FilterData &))); connect(m_basket, SIGNAL(postMessage(const QString &)), Global::bnpView, SLOT(postStatusbarMessage(const QString &))); connect(m_basket, SIGNAL(setStatusBarText(const QString &)), Global::bnpView, SLOT(setStatusBarHint(const QString &))); connect(m_basket, SIGNAL(resetStatusBarText()), Global::bnpView, SLOT(updateStatusBarHint())); } DecoratedBasket::~DecoratedBasket() { } void DecoratedBasket::setFilterBarPosition(bool onTop) { m_layout->removeWidget(m_filter); if (onTop) { m_layout->insertWidget(0, m_filter); setTabOrder(this /*(QWidget*)parent()*/, m_filter); setTabOrder(m_filter, m_basket->graphicsView()); setTabOrder(m_basket->graphicsView(), (QWidget *)parent()); } else { m_layout->addWidget(m_filter); setTabOrder(this /*(QWidget*)parent()*/, m_basket->graphicsView()); setTabOrder(m_basket->graphicsView(), m_filter); setTabOrder(m_filter, (QWidget *)parent()); } } void DecoratedBasket::setFilterBarVisible(bool show, bool switchFocus) { // m_basket->setShowFilterBar(true);//show); // m_basket->save(); // In this order (m_basket and then m_filter) because setShown(false) // will call resetFilter() that will update actions, and then check the // Ctrl+F action whereas it should be unchecked // FIXME: It's very uggly all those things m_filter->setVisible(show); if (show) { if (switchFocus) m_filter->setEditFocus(); } else if (m_filter->hasEditFocus()) m_basket->setFocus(); } void DecoratedBasket::resetFilter() { m_filter->reset(); } void DecoratedBasket::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); m_basket->relayoutNotes(true); } diff --git a/src/decoratedbasket.h b/src/decoratedbasket.h index f07481a..2f5afa3 100644 --- a/src/decoratedbasket.h +++ b/src/decoratedbasket.h @@ -1,68 +1,54 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef DECORATEDBASKET_H #define DECORATEDBASKET_H class QString; class BasketScene; class QVBoxLayout; class QGraphicsView; #include #include "filter.h" /** This class handle Basket and add a FilterWidget on top of it. * @author Sébastien Laoût */ class DecoratedBasket : public QWidget { Q_OBJECT public: DecoratedBasket(QWidget *parent, const QString &folderName, Qt::WindowFlags fl = 0); ~DecoratedBasket() override; void setFilterBarPosition(bool onTop); void resetFilter(); void setFilterBarVisible(bool show, bool switchFocus = true); bool isFilterBarVisible() { return m_filter->isVisible(); } const FilterData &filterData() { return m_filter->filterData(); } FilterBar *filterBar() { return m_filter; } BasketScene *basket() { return m_basket; } void resizeEvent(QResizeEvent *event) override; private: QVBoxLayout *m_layout; FilterBar *m_filter; BasketScene *m_basket; }; #endif // DECORATEDBASKET_H diff --git a/src/diskerrordialog.cpp b/src/diskerrordialog.cpp index 0410f89..a347e4d 100644 --- a/src/diskerrordialog.cpp +++ b/src/diskerrordialog.cpp @@ -1,72 +1,58 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "diskerrordialog.h" #include #include #include #include #include #include #include #include #include #include DiskErrorDialog::DiskErrorDialog(const QString &titleMessage, const QString &message, QWidget *parent) : QDialog(parent) { setObjectName("DiskError"); setWindowTitle(i18n("Save Error")); // enableButtonCancel(false); // enableButtonClose(false); // enableButton(Close, false); // okButton->setEnabled(false); setModal(true); // QHBoxLayout *layout = new QHBoxLayout(mainWidget(), /*margin=*/0, spacingHint()); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); QHBoxLayout *layout = new QHBoxLayout(mainWidget); QPixmap icon = KIconLoader::global()->loadIcon("drive-harddisk", KIconLoader::NoGroup, 64, KIconLoader::DefaultState, QStringList(), /*path_store=*/0L, /*canReturnNull=*/true); QLabel *iconLabel = new QLabel(mainWidget); iconLabel->setPixmap(icon); iconLabel->setFixedSize(iconLabel->sizeHint()); QLabel *label = new QLabel("

" + titleMessage + "

" + message + "

", mainWidget); if (!icon.isNull()) layout->addWidget(iconLabel); layout->addWidget(label); } DiskErrorDialog::~DiskErrorDialog() { } void DiskErrorDialog::closeEvent(QCloseEvent *event) { event->ignore(); } void DiskErrorDialog::keyPressEvent(QKeyEvent *) { // Escape should not close the window... } diff --git a/src/diskerrordialog.h b/src/diskerrordialog.h index 5dfbdc9..92fbb47 100644 --- a/src/diskerrordialog.h +++ b/src/diskerrordialog.h @@ -1,44 +1,30 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef DISKERRORDIALOG_H #define DISKERRORDIALOG_H #include class QCloseEvent; class QKeyEvent; /** Provide a dialog to avert the user the disk is full. * This dialog is modal and is shown until the user has made space on the disk. * @author Sébastien Laoût */ class DiskErrorDialog : public QDialog { Q_OBJECT public: DiskErrorDialog(const QString &titleMessage, const QString &message, QWidget *parent = 0); ~DiskErrorDialog() override; protected: void closeEvent(QCloseEvent *event) override; void keyPressEvent(QKeyEvent *) override; }; #endif // DISKERRORDIALOG_H diff --git a/src/file_metadata.cpp b/src/file_metadata.cpp index b301ffb..6a22ee7 100644 --- a/src/file_metadata.cpp +++ b/src/file_metadata.cpp @@ -1,61 +1,65 @@ +/** + * SPDX-FileCopyrightText: (C) 2015 by Gleb Baryshev + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "file_metadata.h" #include using namespace KFileMetaData::Property; void MetaDataExtractionResult::add(Property property, const QVariant &value) { m_groups[property] = value; } QList> MetaDataExtractionResult::preferredGroups() { // Text captions for some properties. Unspecified properties will get empty captions static const QMap PROPERTY_TRANSLATIONS = {{Property::BitRate, i18n("Bit rate")}, {Property::Channels, i18n("Channels")}, {Property::Duration, i18n("Duration")}, {Property::Genre, i18n("Genre")}, {Property::SampleRate, i18n("Sample rate")}, {Property::TrackNumber, i18n("Track number")}, {Property::Comment, i18n("Comment")}, {Property::Artist, i18n("Artist")}, {Property::Album, i18n("Album")}, {Property::Title, i18n("Title")}, {Property::WordCount, i18n("Word count")}, {Property::LineCount, i18n("Line count")}, {Property::Copyright, i18n("Copyright")}, {Property::CreationDate, i18n("Date")}, {Property::FrameRate, i18n("Frame rate")}}; static QList preferredItems; if (preferredItems.count() == 0) { // According to KDE 3 services/kfile_*.desktop // audio preferredItems << Property::Title << Property::Artist << Property::Album << Property::TrackNumber << Property::Genre << Property::BitRate << Property::Duration << Property::CreationDate << Property::Comment << Property::SampleRate << Property::Channels << Property::Copyright; // video preferredItems /*<< Property::Duration*/ << Property::FrameRate; // text preferredItems << Property::LineCount << Property::WordCount; } QList> result; if (m_groups.count() == 0) return result; KFileMetaData::PropertyMap groups = m_groups; for (int i = 0; i < preferredItems.count(); i++) { QVariant value = groups.take(preferredItems[i]); if (value.isValid()) result.append({PROPERTY_TRANSLATIONS[preferredItems[i]], value.toString()}); } // Remaining groups for (auto it = groups.begin(); it != groups.end(); it++) { result.append({PROPERTY_TRANSLATIONS[it.key()], it.value().toString()}); } return result; } diff --git a/src/file_metadata.h b/src/file_metadata.h index 4695260..0dfca56 100644 --- a/src/file_metadata.h +++ b/src/file_metadata.h @@ -1,52 +1,38 @@ -/* - * Copyright (C) 2015 by Gleb Baryshev - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +/** + * SPDX-FileCopyrightText: (C) 2015 by Gleb Baryshev + * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef FILE_METADATA_H #define FILE_METADATA_H #include #include #include /// Store and retrieve metadata extraction results class MetaDataExtractionResult : public KFileMetaData::ExtractionResult { public: MetaDataExtractionResult(const QString &url, const QString &mimetype) : KFileMetaData::ExtractionResult(url, mimetype, KFileMetaData::ExtractionResult::ExtractMetaData) { } void append(const QString &) override { } // not used void add(KFileMetaData::Property::Property property, const QVariant &value) override; void addType(KFileMetaData::Type::Type) override { } // not used /// Get preferred metadata as "property-value" pairs QList> preferredGroups(); private: KFileMetaData::PropertyMap m_groups; }; #endif // FILE_METADATA_H diff --git a/src/file_mimetypes.h b/src/file_mimetypes.h index 294cd1d..525d02a 100644 --- a/src/file_mimetypes.h +++ b/src/file_mimetypes.h @@ -1,40 +1,26 @@ -/* - * Copyright (C) 2015 by Gleb Baryshev - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +/** + * SPDX-FileCopyrightText: (C) 2015 by Gleb Baryshev + * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef FILE_MIMETYPES_H #define FILE_MIMETYPES_H namespace MimeTypes { #define StrRes static const char *const StrRes LAUNCHER = "application/x-desktop"; StrRes HTML = "text/html"; StrRes TEXT = "text/plain"; StrRes ANIMATION = "image/gif"; StrRes ANIMATION_MNG = "movie/x-mng"; StrRes IMAGE = "image/"; StrRes AUDIO = "audio/"; #undef StrRes } #endif // FILE_MIMETYPES_H diff --git a/src/filter.cpp b/src/filter.cpp index 25e2216..703d1c5 100644 --- a/src/filter.cpp +++ b/src/filter.cpp @@ -1,314 +1,300 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "filter.h" #include #include #include #include #include #include #include #include #include #include #include "bnpview.h" #include "focusedwidgets.h" #include "global.h" #include "tag.h" #include "tools.h" /** FilterBar */ FilterBar::FilterBar(QWidget *parent) : QWidget(parent) /*, m_blinkTimer(this), m_blinkedTimes(0)*/ { QHBoxLayout *hBox = new QHBoxLayout(this); // Create every widgets: // (Aaron Seigo says we don't need to worry about the // "Toolbar group" stuff anymore.) QIcon resetIcon = QIcon::fromTheme("dialog-close"); QIcon inAllIcon = QIcon::fromTheme("edit-find"); m_resetButton = new QToolButton(this); m_resetButton->setIcon(resetIcon); m_resetButton->setText(i18n("Reset Filter")); //, /*groupText=*/"", this, SLOT(reset()), 0); m_resetButton->setAutoRaise(true); // new KToolBarButton("locationbar_erase", /*id=*/1230, this, /*name=*/0, i18n("Reset Filter")); m_lineEdit = new QLineEdit(this); QLabel *label = new QLabel(this); label->setText(i18n("&Filter: ")); label->setBuddy(m_lineEdit); m_tagsBox = new KComboBox(this); QLabel *label2 = new QLabel(this); label2->setText(i18n("T&ag: ")); label2->setBuddy(m_tagsBox); m_inAllBasketsButton = new QToolButton(this); m_inAllBasketsButton->setIcon(inAllIcon); m_inAllBasketsButton->setText(i18n("Filter All Baskets")); //, /*groupText=*/"", this, SLOT(inAllBaskets()), 0); m_inAllBasketsButton->setAutoRaise(true); // Configure the Tags combobox: repopulateTagsCombo(); // Configure the Search in all Baskets button: m_inAllBasketsButton->setCheckable(true); // m_inAllBasketsButton->setChecked(true); // Global::bnpView->toggleFilterAllBaskets(true); // m_lineEdit->setMaximumWidth(150); m_lineEdit->setClearButtonEnabled(true); // Layout all those widgets: hBox->addWidget(m_resetButton); hBox->addWidget(label); hBox->addWidget(m_lineEdit); hBox->addWidget(label2); hBox->addWidget(m_tagsBox); hBox->addWidget(m_inAllBasketsButton); // connect( &m_blinkTimer, SIGNAL(timeout()), this, SLOT(blinkBar()) ); connect(m_resetButton, SIGNAL(clicked()), this, SLOT(reset())); connect(m_lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(changeFilter())); connect(m_tagsBox, SIGNAL(activated(int)), this, SLOT(tagChanged(int))); // connect( m_inAllBasketsButton, SIGNAL(clicked()), this, SLOT(inAllBaskets()) ); m_inAllBasketsButton->setDefaultAction(Global::bnpView->m_actFilterAllBaskets); FocusWidgetFilter *lineEditF = new FocusWidgetFilter(m_lineEdit); m_tagsBox->installEventFilter(lineEditF); connect(lineEditF, SIGNAL(escapePressed()), SLOT(reset())); connect(lineEditF, SIGNAL(returnPressed()), SLOT(changeFilter())); } FilterBar::~FilterBar() { } void FilterBar::setFilterData(const FilterData &data) { m_lineEdit->setText(data.string); int index = 0; switch (data.tagFilterType) { default: case FilterData::DontCareTagsFilter: index = 0; break; case FilterData::NotTaggedFilter: index = 1; break; case FilterData::TaggedFilter: index = 2; break; case FilterData::TagFilter: filterTag(data.tag); return; case FilterData::StateFilter: filterState(data.state); return; } if (m_tagsBox->currentIndex() != index) { m_tagsBox->setCurrentIndex(index); tagChanged(index); } } void FilterBar::repopulateTagsCombo() { static const int ICON_SIZE = 16; m_tagsBox->clear(); m_tagsMap.clear(); m_statesMap.clear(); m_tagsBox->addItem(""); m_tagsBox->addItem(i18n("(Not tagged)")); m_tagsBox->addItem(i18n("(Tagged)")); int index = 3; Tag *tag; State *state; QString icon; QString text; QPixmap emblem; for (Tag::List::iterator it = Tag::all.begin(); it != Tag::all.end(); ++it) { tag = *it; state = tag->states().first(); // Insert the tag in the combo-box: if (tag->countStates() > 1) { text = tag->name(); icon = ""; } else { text = state->name(); icon = state->emblem(); } emblem = KIconLoader::global()->loadIcon(icon, KIconLoader::Desktop, ICON_SIZE, KIconLoader::DefaultState, QStringList(), 0L, /*canReturnNull=*/true); m_tagsBox->insertItem(index, emblem, text); // Update the mapping: m_tagsMap.insert(index, tag); ++index; // Insert sub-states, if needed: if (tag->countStates() > 1) { for (State::List::iterator it2 = tag->states().begin(); it2 != tag->states().end(); ++it2) { state = *it2; // Insert the state: text = state->name(); icon = state->emblem(); emblem = KIconLoader::global()->loadIcon(icon, KIconLoader::Desktop, ICON_SIZE, KIconLoader::DefaultState, QStringList(), 0L, /*canReturnNull=*/true); // Indent the emblem to show the hierarchy relation: if (!emblem.isNull()) emblem = Tools::indentPixmap(emblem, /*depth=*/1, /*deltaX=*/2 * ICON_SIZE / 3); m_tagsBox->insertItem(index, emblem, text); // Update the mapping: m_statesMap.insert(index, state); ++index; } } } } void FilterBar::reset() { m_lineEdit->setText(""); // m_data->isFiltering will be set to false; m_lineEdit->clearFocus(); changeFilter(); if (m_tagsBox->currentIndex() != 0) { m_tagsBox->setCurrentIndex(0); tagChanged(0); } hide(); emit newFilter(m_data); } void FilterBar::filterTag(Tag *tag) { int index = 0; for (QMap::Iterator it = m_tagsMap.begin(); it != m_tagsMap.end(); ++it) if (it.value() == tag) { index = it.key(); break; } if (index <= 0) return; if (m_tagsBox->currentIndex() != index) { m_tagsBox->setCurrentIndex(index); tagChanged(index); } } void FilterBar::filterState(State *state) { int index = 0; for (QMap::Iterator it = m_statesMap.begin(); it != m_statesMap.end(); ++it) if (it.value() == state) { index = it.key(); break; } if (index <= 0) return; if (m_tagsBox->currentIndex() != index) { m_tagsBox->setCurrentIndex(index); tagChanged(index); } } void FilterBar::inAllBaskets() { // TODO! } void FilterBar::setEditFocus() { m_lineEdit->setFocus(); } bool FilterBar::hasEditFocus() { return m_lineEdit->hasFocus() || m_tagsBox->hasFocus(); } const FilterData &FilterBar::filterData() { return m_data; } void FilterBar::changeFilter() { m_data.string = m_lineEdit->text(); m_data.isFiltering = (!m_data.string.isEmpty() || m_data.tagFilterType != FilterData::DontCareTagsFilter); if (hasEditFocus()) m_data.isFiltering = true; emit newFilter(m_data); } void FilterBar::tagChanged(int index) { m_data.tag = 0; m_data.state = 0; switch (index) { case 0: m_data.tagFilterType = FilterData::DontCareTagsFilter; break; case 1: m_data.tagFilterType = FilterData::NotTaggedFilter; break; case 2: m_data.tagFilterType = FilterData::TaggedFilter; break; default: // Try to find if we are filtering a tag: QMap::iterator it = m_tagsMap.find(index); if (it != m_tagsMap.end()) { m_data.tagFilterType = FilterData::TagFilter; m_data.tag = *it; } else { // If not, try to find if we are filtering a state: QMap::iterator it2 = m_statesMap.find(index); if (it2 != m_statesMap.end()) { m_data.tagFilterType = FilterData::StateFilter; m_data.state = *it2; } else { // If not (should never happens), do as if the tags filter was reset: m_data.tagFilterType = FilterData::DontCareTagsFilter; } } break; } m_data.isFiltering = (!m_data.string.isEmpty() || m_data.tagFilterType != FilterData::DontCareTagsFilter); if (hasEditFocus()) m_data.isFiltering = true; emit newFilter(m_data); } diff --git a/src/filter.h b/src/filter.h index a3821ee..a6404b2 100644 --- a/src/filter.h +++ b/src/filter.h @@ -1,102 +1,88 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef FILTER_H #define FILTER_H #include #include class QToolButton; class QLineEdit; class KComboBox; class Tag; class State; /** The structure that contain all filter terms * @author Sébastien Laoût */ struct FilterData { public: // Useful Enum for tagFilterType: enum TagFilterType { DontCareTagsFilter = 0, NotTaggedFilter, TaggedFilter, TagFilter, StateFilter }; // Constructor and Destructor: FilterData() { isFiltering = false; tagFilterType = DontCareTagsFilter; tag = 0; state = 0; } ~FilterData() { } // Filter data: QString string; int tagFilterType; Tag *tag; State *state; bool isFiltering; }; /** A QWidget that allow user to enter terms to filter in a Basket. * @author Sébastien Laoût */ class FilterBar : public QWidget { Q_OBJECT public: explicit FilterBar(QWidget *parent = nullptr); ~FilterBar() override; const FilterData &filterData(); signals: void newFilter(const FilterData &data); public slots: void repopulateTagsCombo(); void reset(); void inAllBaskets(); void setEditFocus(); void filterTag(Tag *tag); void filterState(State *state); void setFilterData(const FilterData &data); public: bool hasEditFocus(); QLineEdit *lineEdit() { return m_lineEdit; } private slots: void changeFilter(); void tagChanged(int index); private: FilterData m_data; QLineEdit *m_lineEdit; QToolButton *m_resetButton; KComboBox *m_tagsBox; QToolButton *m_inAllBasketsButton; QMap m_tagsMap; QMap m_statesMap; }; #endif // FILTER_H diff --git a/src/focusedwidgets.cpp b/src/focusedwidgets.cpp index 8bc07b7..7bd356a 100644 --- a/src/focusedwidgets.cpp +++ b/src/focusedwidgets.cpp @@ -1,149 +1,135 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "focusedwidgets.h" #include #include #include #include #include #include #include "basketscene.h" #include "bnpview.h" #include "global.h" #include "settings.h" #ifdef KeyPress #undef KeyPress #endif /** class FocusedTextEdit */ FocusedTextEdit::FocusedTextEdit(bool disableUpdatesOnKeyPress, QWidget *parent) : KTextEdit(parent) , m_disableUpdatesOnKeyPress(disableUpdatesOnKeyPress) { connect(this, SIGNAL(selectionChanged()), this, SLOT(onSelectionChanged())); } FocusedTextEdit::~FocusedTextEdit() { } void FocusedTextEdit::paste(QClipboard::Mode mode) { const QMimeData *md = QApplication::clipboard()->mimeData(mode); if (md) insertFromMimeData(md); } void FocusedTextEdit::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { emit escapePressed(); return; } if (m_disableUpdatesOnKeyPress) setUpdatesEnabled(false); KTextEdit::keyPressEvent(event); // Workaround (for ensuring the cursor to be visible): signal not emitted when pressing those keys: if (event->key() == Qt::Key_Home || event->key() == Qt::Key_End || event->key() == Qt::Key_PageUp || event->key() == Qt::Key_PageDown) emit cursorPositionChanged(); if (m_disableUpdatesOnKeyPress) { setUpdatesEnabled(true); if (!document()->isEmpty()) ensureCursorVisible(); } } void FocusedTextEdit::wheelEvent(QWheelEvent *event) { // If we're already scrolled all the way to the top or bottom, we pass the // wheel event onto the basket. QScrollBar *sb = verticalScrollBar(); if ((event->delta() > 0 && sb->value() > sb->minimum()) || (event->delta() < 0 && sb->value() < sb->maximum())) KTextEdit::wheelEvent(event); // else // Global::bnpView->currentBasket()->graphicsView()->wheelEvent(event); } void FocusedTextEdit::enterEvent(QEvent *event) { emit mouseEntered(); KTextEdit::enterEvent(event); } void FocusedTextEdit::insertFromMimeData(const QMimeData *source) { // When user always wants plaintext pasting, if both HTML and text data is // present, only send plain text data (the provided source is readonly and I // also can't just pass it to QMimeData constructor as the latter is 'private') if (Settings::pasteAsPlainText() && source->hasHtml() && source->hasText()) { QMimeData alteredSource; alteredSource.setData("text/plain", source->data("text/plain")); KTextEdit::insertFromMimeData(&alteredSource); } else KTextEdit::insertFromMimeData(source); } void FocusedTextEdit::onSelectionChanged() { if (textCursor().selectedText().length() > 0) { QMimeData *md = createMimeDataFromSelection(); QApplication::clipboard()->setMimeData(md, QClipboard::Selection); } } /** class FocusWidgetFilter */ FocusWidgetFilter::FocusWidgetFilter(QWidget *parent) : QObject(parent) { if (parent) parent->installEventFilter(this); } bool FocusWidgetFilter::eventFilter(QObject *, QEvent *e) { switch (e->type()) { case QEvent::KeyPress: { QKeyEvent *ke = static_cast(e); switch (ke->key()) { case Qt::Key_Return: emit returnPressed(); return true; case Qt::Key_Escape: emit escapePressed(); return true; default: return false; }; } case QEvent::Enter: emit mouseEntered(); // pass through default: return false; }; } diff --git a/src/focusedwidgets.h b/src/focusedwidgets.h index 98ed717..49519bf 100644 --- a/src/focusedwidgets.h +++ b/src/focusedwidgets.h @@ -1,88 +1,74 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef FOCUSEDWIDGETS_H #define FOCUSEDWIDGETS_H #include #include class QEvent; class QKeyEvent; class QWheelEvent; class QMenu; class FocusedTextEdit : public KTextEdit { Q_OBJECT public: explicit FocusedTextEdit(bool disableUpdatesOnKeyPress, QWidget *parent = nullptr); ~FocusedTextEdit() override; void paste(QClipboard::Mode mode); public slots: void onSelectionChanged(); //!< Put selected text into the global mouse selection protected: void keyPressEvent(QKeyEvent *event) override; void wheelEvent(QWheelEvent *event) override; void enterEvent(QEvent *event) override; void insertFromMimeData(const QMimeData *source) override; signals: void escapePressed(); void mouseEntered(); private: bool m_disableUpdatesOnKeyPress; }; /** class FocusWidgetFilter * @author Kelvie Wong * * A very simple event filter that returns when escape and return are pressed, * and as well, to emit a signal for the mouse event. * * This allows us to create our own focus model with widgets inside baskets * (although I'm not sure how useful this will all be after we port Basket to be * use QGraphicsView). * * Keypresses are filtered (i.e. the widget will not get the key press events), * but the enterEvent is not (for backwards compatibility). */ class FocusWidgetFilter : public QObject { Q_OBJECT public: /** Constructor * @param watched The widget to install the event filter on; also becomes * the parent of this object. */ explicit FocusWidgetFilter(QWidget *watched = nullptr); ~FocusWidgetFilter() override { } protected: bool eventFilter(QObject *object, QEvent *event) override; signals: void escapePressed(); void returnPressed(); void mouseEntered(); }; #endif // FOCUSEDWIDGETS_H diff --git a/src/formatimporter.cpp b/src/formatimporter.cpp index 7ab963c..9ea18c3 100644 --- a/src/formatimporter.cpp +++ b/src/formatimporter.cpp @@ -1,311 +1,297 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "formatimporter.h" #include #include #include #include #include #include #include #include #include #include #include "basketscene.h" #include "bnpview.h" #include "global.h" #include "notecontent.h" #include "notefactory.h" #include "tools.h" #include "xmlwork.h" bool FormatImporter::shouldImportBaskets() { // We should import if the application have not successfully loaded any basket... if (Global::bnpView->topLevelItemCount() >= 0) return false; // ... And there is at least one folder in the save folder, with a ".basket" file inside that folder. QDir dir(Global::savesFolder(), QString(), QDir::Name | QDir::IgnoreCase, QDir::Dirs | QDir::NoSymLinks); QStringList list = dir.entryList(); for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) if (*it != "." && *it != ".." && dir.exists(Global::savesFolder() + *it + "/.basket")) return true; return false; } void FormatImporter::copyFolder(const QString &folder, const QString &newFolder) { copyFinished = false; KIO::CopyJob *copyJob = KIO::copyAs(QUrl::fromLocalFile(folder), QUrl::fromLocalFile(newFolder), KIO::HideProgressInfo); connect(copyJob, &KIO::CopyJob::copyingDone, this, &FormatImporter::slotCopyingDone); while (!copyFinished) qApp->processEvents(); } void FormatImporter::moveFolder(const QString &folder, const QString &newFolder) { copyFinished = false; KIO::CopyJob *copyJob = KIO::moveAs(QUrl::fromLocalFile(folder), QUrl::fromLocalFile(newFolder), KIO::HideProgressInfo); connect(copyJob, &KIO::CopyJob::copyingDone, this, &FormatImporter::slotCopyingDone); while (!copyFinished) qApp->processEvents(); } void FormatImporter::slotCopyingDone(KIO::Job *) { // qDebug() << "Copy finished of " + from.path() + " to " + to.path(); copyFinished = true; } void FormatImporter::importBaskets() { qDebug() << "Import Baskets: Preparing..."; // Some preliminary preparations (create the destination folders and the basket tree file): QDir dirPrep; dirPrep.mkdir(Global::savesFolder()); dirPrep.mkdir(Global::basketsFolder()); QDomDocument document("basketTree"); QDomElement root = document.createElement("basketTree"); document.appendChild(root); // First up, establish a list of every baskets, ensure the old order (if any), and count them. QStringList baskets; // Read the 0.5.0 baskets order: QDomDocument *doc = XMLWork::openFile("container", Global::savesFolder() + "container.baskets"); if (doc != 0) { QDomElement docElem = doc->documentElement(); QDomElement basketsElem = XMLWork::getElement(docElem, "baskets"); QDomNode n = basketsElem.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); if ((!e.isNull()) && e.tagName() == "basket") baskets.append(e.text()); n = n.nextSibling(); } } // Then load the baskets that weren't loaded (import < 0.5.0 ones): QDir dir(Global::savesFolder(), QString(), QDir::Name | QDir::IgnoreCase, QDir::Dirs | QDir::NoSymLinks); QStringList list = dir.entryList(); if (list.count() > 2) // Pass "." and ".." for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) // For each folder if (*it != "." && *it != ".." && dir.exists(Global::savesFolder() + *it + "/.basket")) // If it can be a basket folder if (!(baskets.contains((*it) + '/')) && baskets.contains(*it)) // And if it is not already in the imported baskets list baskets.append(*it); qDebug() << "Import Baskets: Found " << baskets.count() << " baskets to import."; // Import every baskets: int i = 0; for (QStringList::iterator it = baskets.begin(); it != baskets.end(); ++it) { ++i; qDebug() << "Import Baskets: Importing basket " << i << " of " << baskets.count() << "..."; // Move the folder to the new repository (normal basket) or copy the folder (mirrored folder): QString folderName = *it; if (folderName.startsWith('/')) { // It was a folder mirror: KMessageBox::information(0, i18n("

Folder mirroring is not possible anymore (see basket-notepads.github.io for more information).

" "

The folder %1 has been copied for the basket needs. You can either delete this folder or delete the basket, or use both. But remember that " "modifying one will not modify the other anymore as they are now separate entities.

", folderName), i18n("Folder Mirror Import"), "", KMessageBox::AllowLink); // Also modify folderName to be only the folder name and not the full path anymore: QString newFolderName = folderName; if (newFolderName.endsWith('/')) newFolderName = newFolderName.left(newFolderName.length() - 1); newFolderName = newFolderName.mid(newFolderName.lastIndexOf('/') + 1); newFolderName = Tools::fileNameForNewFile(newFolderName, Global::basketsFolder()); FormatImporter f; f.copyFolder(folderName, Global::basketsFolder() + newFolderName); folderName = newFolderName; } else dir.rename(Global::savesFolder() + folderName, Global::basketsFolder() + folderName); // Move the folder // Import the basket structure file and get the properties (to add them in the tree basket-properties cache): QDomElement properties = importBasket(folderName); // Add it to the XML document: QDomElement basketElement = document.createElement("basket"); root.appendChild(basketElement); basketElement.setAttribute("folderName", folderName); basketElement.appendChild(properties); } // Finalize (write to disk and delete now useless files): qDebug() << "Import Baskets: Finalizing..."; QFile file(Global::basketsFolder() + "baskets.xml"); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream.setCodec("UTF-8"); QString xml = document.toString(); stream << "\n"; stream << xml; file.close(); } Tools::deleteRecursively(Global::savesFolder() + ".tmp"); dir.remove(Global::savesFolder() + "container.baskets"); qDebug() << "Import Baskets: Finished."; } QDomElement FormatImporter::importBasket(const QString &folderName) { // Load the XML file: QDomDocument *document = XMLWork::openFile("basket", Global::basketsFolder() + folderName + "/.basket"); if (!document) { qDebug() << "Import Baskets: Failed to read the basket file!"; return QDomElement(); } QDomElement docElem = document->documentElement(); // Import properties (change to , and figure out if is a checklist or not): QDomElement properties = XMLWork::getElement(docElem, "properties"); QDomElement background = XMLWork::getElement(properties, "background"); QColor backgroundColor = QColor(background.attribute("color")); if (backgroundColor.isValid() && (backgroundColor != qApp->palette().color(QPalette::Base))) { // Use the default color if it was already that color: QDomElement appearance = document->createElement("appearance"); appearance.setAttribute("backgroundColor", backgroundColor.name()); properties.appendChild(appearance); } QDomElement disposition = document->createElement("disposition"); disposition.setAttribute("mindMap", "false"); disposition.setAttribute("columnCount", "1"); disposition.setAttribute("free", "false"); bool isCheckList = XMLWork::trueOrFalse(XMLWork::getElementText(properties, "showCheckBoxes"), false); // Insert all notes in a group (column): 1/ rename "items" to "group", 2/ add "notes" to root, 3/ move "group" into "notes" QDomElement column = XMLWork::getElement(docElem, "items"); column.setTagName("group"); QDomElement notes = document->createElement("notes"); notes.appendChild(column); docElem.appendChild(notes); // Import notes from older representations: QDomNode n = column.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { e.setTagName("note"); QDomElement content = XMLWork::getElement(e, "content"); // Add Check tag: if (isCheckList) { bool isChecked = XMLWork::trueOrFalse(e.attribute("checked", "false")); XMLWork::addElement(*document, e, "tags", (isChecked ? "todo_done" : "todo_unchecked")); } // Import annotations as folded groups: QDomElement parentE = column; QString annotations = XMLWork::getElementText(e, "annotations", ""); if (!annotations.isEmpty()) { QDomElement annotGroup = document->createElement("group"); column.insertBefore(annotGroup, e); annotGroup.setAttribute("folded", "true"); annotGroup.appendChild(e); parentE = annotGroup; // Create the text note and add it to the DOM tree: QDomElement annotNote = document->createElement("note"); annotNote.setAttribute("type", "text"); annotGroup.appendChild(annotNote); QString annotFileName = Tools::fileNameForNewFile("annotations1.txt", BasketScene::fullPathForFolderName(folderName)); QString annotFullPath = BasketScene::fullPathForFolderName(folderName) + '/' + annotFileName; QFile file(annotFullPath); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream << annotations; file.close(); } XMLWork::addElement(*document, annotNote, "content", annotFileName); n = annotGroup; } // Import Launchers from 0.3.x, 0.4.0 and 0.5.0-alphas: QString runCommand = e.attribute("runcommand"); // Keep compatibility with 0.4.0 and 0.5.0-alphas versions runCommand = XMLWork::getElementText(e, "action", runCommand); // Keep compatibility with 0.3.x versions if (!runCommand.isEmpty()) { // An import should be done // Prepare the launcher note: QString title = content.attribute("title", ""); QString icon = content.attribute("icon", ""); if (title.isEmpty()) title = runCommand; if (icon.isEmpty()) icon = NoteFactory::iconForCommand(runCommand); // Import the launcher note: // Adapted version of "QString launcherName = NoteFactory::createNoteLauncherFile(runCommand, title, icon, this)": QString launcherContent = QString( "[Desktop Entry]\n" "Exec=%1\n" "Name=%2\n" "Icon=%3\n" "Encoding=UTF-8\n" "Type=Application\n") .arg(runCommand, title, icon.isEmpty() ? QString("exec") : icon); QString launcherFileName = Tools::fileNameForNewFile("launcher.desktop", Global::basketsFolder() + folderName /*+ "/"*/); QString launcherFullPath = Global::basketsFolder() + folderName /*+ "/"*/ + launcherFileName; QFile file(launcherFullPath); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream.setCodec("UTF-8"); stream << launcherContent; file.close(); } // Add the element to the DOM: QDomElement launcherElem = document->createElement("note"); parentE.insertBefore(launcherElem, e); launcherElem.setAttribute("type", "launcher"); XMLWork::addElement(*document, launcherElem, "content", launcherFileName); } // Import unknown ns to 0.6.0: if (e.attribute("type") == "unknow") e.setAttribute("type", "unknown"); // Import links from version < 0.5.0: if (!content.attribute("autotitle").isEmpty() && content.attribute("autoTitle").isEmpty()) content.setAttribute("autoTitle", content.attribute("autotitle")); if (!content.attribute("autoicon").isEmpty() && content.attribute("autoIcon").isEmpty()) content.setAttribute("autoIcon", content.attribute("autoicon")); } n = n.nextSibling(); } // Save the resulting XML file: QFile file(Global::basketsFolder() + folderName + "/.basket"); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream.setCodec("UTF-8"); // QString xml = document->toString(); // stream << "\n"; // stream << xml; stream << document->toString(); // Document is ALREADY using UTF-8 file.close(); } else qDebug() << "Import Baskets: Failed to save the basket file!"; // Return the newly created properties (to put in the basket tree): return properties; } diff --git a/src/formatimporter.h b/src/formatimporter.h index e39f04e..358ca86 100644 --- a/src/formatimporter.h +++ b/src/formatimporter.h @@ -1,52 +1,38 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef FORMATIMPORTER_H #define FORMATIMPORTER_H #include class QDomElement; namespace KIO { class Job; } /** * @author Sébastien Laoût */ class FormatImporter : public QObject { Q_OBJECT public: static bool shouldImportBaskets(); static void importBaskets(); static QDomElement importBasket(const QString &folderName); void copyFolder(const QString &folder, const QString &newFolder); void moveFolder(const QString &folder, const QString &newFolder); private slots: void slotCopyingDone(KIO::Job *); private: bool copyFinished; }; #endif // FORMATIMPORTER_H diff --git a/src/gitwrapper.cpp b/src/gitwrapper.cpp index 0cf907a..a44c90e 100644 --- a/src/gitwrapper.cpp +++ b/src/gitwrapper.cpp @@ -1,453 +1,459 @@ +/** + * SPDX-FileCopyrightText: (C) 2014 Narfinger + * SPDX-FileCopyrightText: (C) 2014 Gleb Baryshev + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include #include #include #include #include #include "basketscene.h" #include "gitwrapper.h" #include "settings.h" #ifdef WITH_LIBGIT2 extern "C" { #include } #include "global.h" #define GIT_RETURN_IF_DISABLED() \ if (!Settings::versionSyncEnabled()) \ return; QMutex GitWrapper::gitMutex; void GitWrapper::initializeGitRepository(QString folder) { GIT_RETURN_IF_DISABLED() QMutexLocker l(&gitMutex); // this is not thread safe, we use locking elsewhere git_repository *repo = NULL; QByteArray ba = folder.toUtf8(); const char *cString = ba.data(); int error = git_repository_init(&repo, cString, false); if (error < 0) { const git_error *e = giterr_last(); qDebug() << e->message; } git_signature *sig = NULL; git_index *index = NULL; git_oid tree_id; git_oid commit_id; git_tree *tree = NULL; // no error handling at the moment git_signature_now(&sig, "AutoGit", "auto@localhost"); git_repository_index(&index, repo); git_index_write_tree(&tree_id, index); git_tree_lookup(&tree, repo, &tree_id); git_commit_create_v(&commit_id, repo, "HEAD", sig, sig, NULL, "Initial commit", tree, 0); git_signature_free(sig); git_index_free(index); git_tree_free(tree); // first commit commitPattern(repo, "*", "Initial full commit"); git_repository_free(repo); } void GitWrapper::commitBasketView() { GIT_RETURN_IF_DISABLED() QMutexLocker l(&gitMutex); git_repository *repo = openRepository(); if (repo == 0) return; const QDateTime gitdate = getLastCommitDate(repo); const QString pathtosave = Global::savesFolder(); QDir basketdir(pathtosave + "baskets/"); bool changed = false; basketdir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); // this automatically skips baskets.xml file QDirIterator it(basketdir); while (!changed && it.hasNext()) { const QFileInfo file(it.next()); if (file.lastModified() > gitdate) changed = true; } if (changed) { commitPattern(repo, "*"); } git_repository_free(repo); } void GitWrapper::commitCreateBasket() { GIT_RETURN_IF_DISABLED() QMutexLocker l(&gitMutex); git_repository *repo = openRepository(); if (repo == 0) return; const QDateTime gitdate = getLastCommitDate(repo); const QString basketxml = Global::savesFolder() + "baskets/baskets.xml"; const QFileInfo basketxmlinfo(basketxml); if (gitdate <= basketxmlinfo.lastModified()) { git_index *index = NULL; int error = git_repository_index(&index, repo); if (error < 0) { gitErrorHandling(); return; } // this is kind of hacky because somebody could come in between and we still have stuff open // change basket.xml const QString basketxml("baskets/baskets.xml"); QByteArray basketxmlba = basketxml.toUtf8(); char *basketxmlCString = basketxmlba.data(); error = git_index_add_bypath(index, basketxmlCString); if (error < 0) { gitErrorHandling(); return; } bool result = commitIndex(repo, index); git_index_free(index); } git_repository_free(repo); } void GitWrapper::commitTagsXml() { GIT_RETURN_IF_DISABLED() QMutexLocker l(&gitMutex); git_repository *repo = openRepository(); if (repo == 0) return; git_index *index = NULL; int error = git_repository_index(&index, repo); if (error < 0) { gitErrorHandling(); return; } const QString tagsxml("tags.xml"); QByteArray tagsxmlba = tagsxml.toUtf8(); char *tagsxmlCString = tagsxmlba.data(); error = git_index_add_bypath(index, tagsxmlCString); bool result = commitIndex(repo, index); git_index_free(index); git_repository_free(repo); } void GitWrapper::commitDeleteBasket(QString basketFolderName) { GIT_RETURN_IF_DISABLED() QMutexLocker l(&gitMutex); git_index *index = NULL; git_repository *repo = openRepository(); if (repo == 0) return; int error = git_repository_index(&index, repo); if (error < 0) { gitErrorHandling(); return; } // remove the directory const QString dir("baskets/" + basketFolderName); const QByteArray dirba = dir.toUtf8(); const char *dirCString = dirba.data(); error = git_index_remove_directory(index, dirCString, 0); if (error < 0) { gitErrorHandling(); return; } // change basket.xml const QString basketxml("baskets/baskets.xml"); QByteArray basketxmlba = basketxml.toUtf8(); char *basketxmlCString = basketxmlba.data(); error = git_index_add_bypath(index, basketxmlCString); if (error < 0) { gitErrorHandling(); return; } removeDeletedFromIndex(repo, index); bool result = commitIndex(repo, index); git_index_free(index); git_repository_free(repo); } void GitWrapper::commitBasket(BasketScene *basket) { GIT_RETURN_IF_DISABLED() QMutexLocker l(&gitMutex); git_repository *repo = openRepository(); if (repo == 0) return; const QDateTime gitdate = getLastCommitDate(repo); const QString fullpath = basket->fullPath(); const QDir basketdir(fullpath); bool changed = false; QDirIterator it(basketdir); while (!changed && it.hasNext()) { const QFileInfo file(it.next()); if (file.fileName() != ".basket") { if (file.lastModified() >= gitdate) changed = true; } } if (changed) { git_index *index = NULL; int error = git_repository_index(&index, repo); if (error < 0) { gitErrorHandling(); return; } const QString pattern("baskets/" + basket->folderName() + '*'); QByteArray patternba = pattern.toUtf8(); char *patternCString = patternba.data(); git_strarray arr = {&patternCString, 1}; error = git_index_add_all(index, &arr, 0, 0, 0); if (error < 0) { gitErrorHandling(); return; } const QString basketxml("baskets/baskets.xml"); QByteArray basketxmlba = basketxml.toUtf8(); char *basketxmlCString = basketxmlba.data(); error = git_index_add_bypath(index, basketxmlCString); if (error < 0) { gitErrorHandling(); return; } removeDeletedFromIndex(repo, index); bool result = commitIndex(repo, index); git_index_free(index); } git_repository_free(repo); } bool GitWrapper::commitPattern(git_repository *repo, QString pattern, QString message) { git_index *index = NULL; int error = git_repository_index(&index, repo); if (error < 0) { gitErrorHandling(); return false; } QByteArray patternba = pattern.toUtf8(); char *patternCString = patternba.data(); git_strarray arr = {&patternCString, 1}; error = git_index_add_all(index, &arr, 0, 0, 0); if (error < 0) { gitErrorHandling(); return false; } bool result = commitIndex(repo, index, message); git_index_free(index); return true; } bool GitWrapper::commitIndex(git_repository *repo, git_index *index, QString message) { // write git index git_signature *sig = NULL; git_oid tree_id; git_oid commit_id; git_tree *tree = NULL; int error = git_signature_now(&sig, "AutoGit", "auto@localhost"); if (error < 0) { gitErrorHandling(); return false; } error = git_repository_index(&index, repo); if (error < 0) { gitErrorHandling(); return false; } git_commit *commit = NULL; /* parent */ git_oid oid_parent_commit; /* the SHA1 for last commit */ error = git_reference_name_to_id(&oid_parent_commit, repo, "HEAD"); if (error < 0) { gitErrorHandling(); return false; } error = git_commit_lookup(&commit, repo, &oid_parent_commit); if (error < 0) { gitErrorHandling(); return false; } error = git_index_write(index); if (error < 0) { gitErrorHandling(); return false; } error = git_index_write_tree(&tree_id, index); if (error < 0) { gitErrorHandling(); return false; } error = git_tree_lookup(&tree, repo, &tree_id); if (error < 0) { gitErrorHandling(); return false; } const git_commit *parentarray[] = {commit}; QByteArray commitmessageba = message.toUtf8(); const char *commitmessageCString = commitmessageba.data(); error = git_commit_create(&commit_id, repo, "HEAD", sig, sig, NULL, commitmessageCString, tree, 1, parentarray); if (error < 0) { gitErrorHandling(); return false; } git_signature_free(sig); git_tree_free(tree); return true; } void GitWrapper::removeDeletedFromIndex(git_repository *repo, git_index *index) { git_status_foreach( repo, [](const char *path, unsigned int status_flags, void *payload) -> int { if (status_flags & GIT_STATUS_WT_DELETED) { git_index *index = static_cast(payload); git_index_remove_bypath(index, path); } return 0; }, index); } git_repository *GitWrapper::openRepository() { QString pathtosave = Global::savesFolder(); QByteArray pathba = pathtosave.toUtf8(); const char *pathCString = pathba.data(); git_repository *repo = NULL; int error = git_repository_open(&repo, pathCString); if (error < 0) { gitErrorHandling(); return repo; } return repo; } QDateTime GitWrapper::getLastCommitDate(git_repository *repo) { if (repo == 0) return QDateTime(); git_oid oid_parent_commit; /* the SHA1 for last commit */ int error = git_reference_name_to_id(&oid_parent_commit, repo, "HEAD"); if (error < 0) return QDateTime(); git_commit *head = NULL; error = git_commit_lookup(&head, repo, &oid_parent_commit); if (error < 0) return QDateTime(); int64_t time = static_cast(git_commit_time(head)); QDateTime date; date.setTime_t(time); git_commit_free(head); return date; } void GitWrapper::gitErrorHandling() { const git_error *e = giterr_last(); qDebug() << "Error in git (error,class,message)" << e->klass << e->message; } #else // make everything noop void GitWrapper::initializeGitRepository(QString folder) { } void GitWrapper::commitBasketView() { } void GitWrapper::commitCreateBasket() { } void GitWrapper::commitDeleteBasket(QString basketFolderName) { } void GitWrapper::commitBasket(BasketScene *basket) { } void GitWrapper::commitTagsXml() { } bool GitWrapper::commitPattern(git_repository *repo, QString pattern, QString message) { return true; } bool GitWrapper::commitIndex(git_repository *repo, git_index *index, QString message) { return true; } void GitWrapper::removeDeletedFromIndex(git_repository *repo, git_index *index) { } git_repository *GitWrapper::openRepository() { return 0; } QDateTime GitWrapper::getLastCommitDate(git_repository *repo) { return QDateTime(); } void GitWrapper::gitErrorHandling() { } #endif diff --git a/src/gitwrapper.h b/src/gitwrapper.h index b5edd96..f82ad09 100644 --- a/src/gitwrapper.h +++ b/src/gitwrapper.h @@ -1,34 +1,41 @@ +/** + * SPDX-FileCopyrightText: (C) 2014 Narfinger + * SPDX-FileCopyrightText: (C) 2014 Gleb Baryshev + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + #ifndef GITWRAPPER_H #define GITWRAPPER_H #include class git_repository; class git_index; class BasketScene; /* Static class to encapsulate git operations * * the commit* operations check if the file or subfolder is newer than the git directory **/ class GitWrapper { public: static QMutex gitMutex; static void initializeGitRepository(QString folder); static void commitBasketView(); // commits the whole directory static void commitCreateBasket(); // commits and checks baskets/baskets.xml static void commitDeleteBasket(QString basketFolderName); // deletes a basket directory static void commitBasket(BasketScene *basket); // commits and checks baskets/$BASKETNAME static void commitTagsXml(); // commits and checks baskets.xml private: static bool commitPattern(git_repository *repo, QString pattern = "*", QString message = "AutoCommit"); static bool commitIndex(git_repository *repo, git_index *index, QString message = "AutoCommit"); static void removeDeletedFromIndex(git_repository *repo, git_index *index); static git_repository *openRepository(); static QDateTime getLastCommitDate(git_repository *repo); static void gitErrorHandling(); }; #endif // GITWRAPPER_H diff --git a/src/global.cpp b/src/global.cpp index 92da33a..46e2145 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -1,122 +1,109 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "global.h" #include #include #include #include #include #include #include "bnpview.h" #include "gitwrapper.h" #include "settings.h" /** Define initial values for global variables : */ QString Global::s_customSavesFolder = ""; LikeBack *Global::likeBack = 0L; DebugWindow *Global::debugWindow = 0L; BackgroundManager *Global::backgroundManager = 0L; SystemTray *Global::systemTray = 0L; BNPView *Global::bnpView = 0L; KSharedConfig::Ptr Global::basketConfig; QCommandLineParser *Global::commandLineOpts = NULL; MainWindow *Global::mainWnd = NULL; void Global::setCustomSavesFolder(const QString &folder) { s_customSavesFolder = folder; } QString Global::savesFolder() { static QString *folder = 0L; // Memorize the folder to do not have to re-compute it each time it's needed if (folder == 0L) { // Initialize it if not yet done if (!s_customSavesFolder.isEmpty()) { // Passed by command line (for development & debug purpose) QDir dir; dir.mkdir(s_customSavesFolder); folder = new QString(s_customSavesFolder.endsWith("/") ? s_customSavesFolder : s_customSavesFolder + '/'); } else if (!Settings::dataFolder().isEmpty()) { // Set by config option (in Basket -> Backup & Restore) folder = new QString(Settings::dataFolder().endsWith("/") ? Settings::dataFolder() : Settings::dataFolder() + '/'); } else { // The default path (should be that for most computers) folder = new QString(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "basket/"); initializeGitIfNeeded(*folder); } } return *folder; } QString Global::basketsFolder() { return savesFolder() + "baskets/"; } QString Global::backgroundsFolder() { return savesFolder() + "backgrounds/"; } QString Global::templatesFolder() { return savesFolder() + "templates/"; } QString Global::tempCutFolder() { return savesFolder() + "temp-cut/"; } QString Global::gitFolder() { return savesFolder() + ".git/"; } void Global::initializeGitIfNeeded(QString savesFolder) { if (!QDir(savesFolder + ".git/").exists()) { GitWrapper::initializeGitRepository(savesFolder); } } QString Global::openNoteIcon() // FIXME: Now an edit icon { return QVariant(Global::bnpView->m_actEditNote->icon()).toString(); } KMainWindow *Global::activeMainWindow() { QWidget *res = qApp->activeWindow(); if (res && res->inherits("KMainWindow")) { return static_cast(res); } return 0; } MainWindow *Global::mainWindow() { return mainWnd; } KConfig *Global::config() { // The correct solution is to go and replace all KConfig* with KSharedConfig::Ptr, but that seems awfully annoying to do right now return Global::basketConfig.data(); } diff --git a/src/global.h b/src/global.h index f861447..ba83364 100644 --- a/src/global.h +++ b/src/global.h @@ -1,81 +1,68 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef GLOBAL_H #define GLOBAL_H #include "basket_export.h" #include class QString; class KMainWindow; class LikeBack; class DebugWindow; class BackgroundManager; class SystemTray; class BNPView; class QCommandLineParser; class MainWindow; /** Handle all global variables of the application. * This file only declare classes : developer should include * the .h files of variables he use. * @author Sébastien Laoût */ class BASKET_EXPORT Global { private: static QString s_customSavesFolder; static void initializeGitRepository(QString folder); public: // Global Variables: static LikeBack *likeBack; static DebugWindow *debugWindow; static BackgroundManager *backgroundManager; static SystemTray *systemTray; static BNPView *bnpView; static KSharedConfig::Ptr basketConfig; static QCommandLineParser *commandLineOpts; static MainWindow *mainWnd; // Application Folders: static void setCustomSavesFolder(const QString &folder); static QString savesFolder(); /// << @return e.g. "/home/username/.local/share/basket/". static QString basketsFolder(); /// << @return e.g. "/home/username/.local/share/basket/baskets/". static QString backgroundsFolder(); /// << @return e.g. "/home/username/.local/share/basket/backgrounds/". static QString templatesFolder(); /// << @return e.g. "/home/username/.local/share/basket/templates/". static QString tempCutFolder(); /// << @return e.g. "/home/username/.local/share/basket/temp-cut/". (was ".tmp/") static QString gitFolder(); /// << @return e.g. "/home/username/.local/share/basket/.git/". // Various Things: /** Initialize git repository if Version sync is enabled @param savesFolder Path returned by savesFolder() */ static void initializeGitIfNeeded(QString savesFolder); static QString openNoteIcon(); /// << @return the icon used for the "Open" action on notes. static KMainWindow *activeMainWindow(); /// << @returns Main window if it has focus (is active), otherwise NULL static MainWindow *mainWindow(); /// << @returns Main window (always not NULL after it has been actually created) static KConfig *config(); }; #endif // GLOBAL_H diff --git a/src/history.cpp b/src/history.cpp index 1569ca1..9c4f824 100644 --- a/src/history.cpp +++ b/src/history.cpp @@ -1,48 +1,34 @@ -/*************************************************************************** - * Copyright (C) 2010 Brian C. Milco * - * bcmilco@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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2010 Brian C. Milco + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "history.h" #include "global.h" #include "basketscene.h" #include "bnpview.h" #include #include HistorySetBasket::HistorySetBasket(BasketScene *basket, QUndoCommand *parent) : QUndoCommand(parent) { setText(i18n("Set current basket to %1", basket->basketName())); m_folderNameOld = Global::bnpView->currentBasket()->folderName(); m_folderNameNew = basket->folderName(); } void HistorySetBasket::undo() { BasketScene *oldBasket = Global::bnpView->basketForFolderName(m_folderNameOld); Global::bnpView->setCurrentBasket(oldBasket); } void HistorySetBasket::redo() { BasketScene *curBasket = Global::bnpView->basketForFolderName(m_folderNameNew); Global::bnpView->setCurrentBasket(curBasket); } diff --git a/src/history.h b/src/history.h index 2667b01..fddd2b5 100644 --- a/src/history.h +++ b/src/history.h @@ -1,40 +1,26 @@ -/*************************************************************************** - * Copyright (C) 2010 Brian C. Milco * - * bcmilco@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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2010 Brian C. Milco + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef HISTORY_H #define HISTORY_H #include class BasketScene; class HistorySetBasket : public QUndoCommand { public: explicit HistorySetBasket(BasketScene *basket, QUndoCommand *parent = 0); void undo() override; void redo() override; private: QString m_folderNameOld; QString m_folderNameNew; }; #endif // HISTORY_H diff --git a/src/htmlexporter.cpp b/src/htmlexporter.cpp index 7e61202..c5f9b5e 100644 --- a/src/htmlexporter.cpp +++ b/src/htmlexporter.cpp @@ -1,539 +1,526 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "htmlexporter.h" #include "basketlistview.h" #include "basketscene.h" #include "bnpview.h" #include "config.h" #include "linklabel.h" #include "note.h" #include "notecontent.h" #include "tools.h" #include #include #include #include #include #include #include //For KIO::copy #include //For KIO::file_copy #include #include #include #include #include #include #include #include #include #include HTMLExporter::HTMLExporter(BasketScene *basket) : dialog(new QProgressDialog()) { QDir dir; // Compute a default file name & path: KConfigGroup config = Global::config()->group("Export to HTML"); QString folder = config.readEntry("lastFolder", QDir::homePath()) + '/'; QString url = folder + QString(basket->basketName()).replace('/', '_') + ".html"; // Ask a file name & path to the user: QString filter = "*.html *.htm|" + i18n("HTML Documents") + "\n*|" + i18n("All Files"); QString destination = url; for (bool askAgain = true; askAgain;) { // Ask: destination = QFileDialog::getSaveFileName(NULL, i18n("Export to HTML"), destination, filter); // User canceled? if (destination.isEmpty()) return; // File already existing? Ask for overriding: if (dir.exists(destination)) { int result = KMessageBox::questionYesNoCancel( 0, "" + i18n("The file %1 already exists. Do you really want to override it?", QUrl::fromLocalFile(destination).fileName()), i18n("Override File?"), KGuiItem(i18n("&Override"), "document-save")); if (result == KMessageBox::Cancel) return; else if (result == KMessageBox::Yes) askAgain = false; } else askAgain = false; } // Create the progress dialog that will always be shown during the export: dialog->setWindowTitle(i18n("Export to HTML")); dialog->setLabelText(i18n("Exporting to HTML. Please wait...")); dialog->setCancelButton(NULL); dialog->setAutoClose(true); dialog->show(); // Remember the last folder used for HTML exploration: config.writeEntry("lastFolder", QUrl::fromLocalFile(destination).adjusted(QUrl::RemoveFilename).path()); config.sync(); prepareExport(basket, destination); exportBasket(basket, /*isSubBasketScene*/ false); dialog->setValue(dialog->value() + 1); // Finishing finished } HTMLExporter::~HTMLExporter() { } void HTMLExporter::prepareExport(BasketScene *basket, const QString &fullPath) { dialog->setRange(0, /*Preparation:*/ 1 + /*Finishing:*/ 1 + /*Basket:*/ 1 + /*SubBaskets:*/ Global::bnpView->basketCount(Global::bnpView->listViewItemForBasket(basket))); dialog->setValue(0); qApp->processEvents(); // Remember the file path chosen by the user: filePath = fullPath; fileName = QUrl::fromLocalFile(fullPath).fileName(); exportedBasket = basket; currentBasket = 0; BasketListViewItem *item = Global::bnpView->listViewItemForBasket(basket); withBasketTree = (item->childCount() >= 0); // Create and empty the files folder: QString filesFolderPath = i18nc("HTML export folder (files)", "%1_files", filePath) + '/'; // eg.: "/home/seb/foo.html_files/" Tools::deleteRecursively(filesFolderPath); QDir dir; dir.mkdir(filesFolderPath); // Create sub-folders: iconsFolderPath = filesFolderPath + i18nc("HTML export folder (icons)", "icons") + '/'; // eg.: "/home/seb/foo.html_files/icons/" imagesFolderPath = filesFolderPath + i18nc("HTML export folder (images)", "images") + '/'; // eg.: "/home/seb/foo.html_files/images/" basketsFolderPath = filesFolderPath + i18nc("HTML export folder (baskets)", "baskets") + '/'; // eg.: "/home/seb/foo.html_files/baskets/" dir.mkdir(iconsFolderPath); dir.mkdir(imagesFolderPath); dir.mkdir(basketsFolderPath); dialog->setValue(dialog->value() + 1); // Preparation finished } void HTMLExporter::exportBasket(BasketScene *basket, bool isSubBasket) { if (!basket->isLoaded()) { basket->load(); } currentBasket = basket; // Compute the absolute & relative paths for this basket: filesFolderPath = i18nc("HTML export folder (files)", "%1_files", filePath) + '/'; if (isSubBasket) { basketFilePath = basketsFolderPath + basket->folderName().left(basket->folderName().length() - 1) + ".html"; filesFolderName = "../"; dataFolderName = basket->folderName().left(basket->folderName().length() - 1) + '-' + i18nc("HTML export folder (data)", "data") + '/'; dataFolderPath = basketsFolderPath + dataFolderName; basketsFolderName = ""; } else { basketFilePath = filePath; filesFolderName = i18nc("HTML export folder (files)", "%1_files", QUrl::fromLocalFile(filePath).fileName()) + '/'; dataFolderName = filesFolderName + i18nc("HTML export folder (data)", "data") + '/'; dataFolderPath = filesFolderPath + i18nc("HTML export folder (data)", "data") + '/'; basketsFolderName = filesFolderName + i18nc("HTML export folder (baskets)", "baskets") + '/'; } iconsFolderName = (isSubBasket ? "../" : filesFolderName) + i18nc("HTML export folder (icons)", "icons") + '/'; // eg.: "foo.html_files/icons/" or "../icons/" imagesFolderName = (isSubBasket ? "../" : filesFolderName) + i18nc("HTML export folder (images)", "images") + '/'; // eg.: "foo.html_files/images/" or "../images/" qDebug() << "Exporting ================================================"; qDebug() << " filePath:" << filePath; qDebug() << " basketFilePath:" << basketFilePath; qDebug() << " filesFolderPath:" << filesFolderPath; qDebug() << " filesFolderName:" << filesFolderName; qDebug() << " iconsFolderPath:" << iconsFolderPath; qDebug() << " iconsFolderName:" << iconsFolderName; qDebug() << " imagesFolderPath:" << imagesFolderPath; qDebug() << " imagesFolderName:" << imagesFolderName; qDebug() << " dataFolderPath:" << dataFolderPath; qDebug() << " dataFolderName:" << dataFolderName; qDebug() << " basketsFolderPath:" << basketsFolderPath; qDebug() << " basketsFolderName:" << basketsFolderName; // Create the data folder for this basket: QDir dir; dir.mkdir(dataFolderPath); backgroundColorName = basket->backgroundColor().name().toLower().mid(1); // Generate basket icons: QString basketIcon16 = iconsFolderName + copyIcon(basket->icon(), 16); QString basketIcon32 = iconsFolderName + copyIcon(basket->icon(), 32); // Generate the [+] image for groups: QPixmap expandGroup(Note::EXPANDER_WIDTH, Note::EXPANDER_HEIGHT); expandGroup.fill(basket->backgroundColor()); QPainter painter(&expandGroup); Note::drawExpander(&painter, 0, 0, basket->backgroundColor(), /*expand=*/true, basket); painter.end(); expandGroup.save(imagesFolderPath + "expand_group_" + backgroundColorName + ".png", "PNG"); // Generate the [-] image for groups: QPixmap foldGroup(Note::EXPANDER_WIDTH, Note::EXPANDER_HEIGHT); foldGroup.fill(basket->backgroundColor()); painter.begin(&foldGroup); Note::drawExpander(&painter, 0, 0, basket->backgroundColor(), /*expand=*/false, basket); painter.end(); foldGroup.save(imagesFolderPath + "fold_group_" + backgroundColorName + ".png", "PNG"); // Open the file to write: QFile file(basketFilePath); if (!file.open(QIODevice::WriteOnly)) return; stream.setDevice(&file); stream.setCodec("UTF-8"); // Output the header: QString borderColor = Tools::mixColor(basket->backgroundColor(), basket->textColor()).name(); stream << "\n" "\n" " \n" " \n" " \n" " \n" " " << Tools::textToHTMLWithoutP(basket->basketName()) << "\n" " \n"; // Create the column handle image: QPixmap columnHandle(Note::RESIZER_WIDTH, 50); painter.begin(&columnHandle); Note::drawInactiveResizer(&painter, 0, 0, columnHandle.height(), basket->backgroundColor(), /*column=*/true); painter.end(); columnHandle.save(imagesFolderPath + "column_handle_" + backgroundColorName + ".png", "PNG"); stream << " \n" " \n" "

\"\" " << Tools::textToHTMLWithoutP(basket->basketName()) << "

\n"; if (withBasketTree) writeBasketTree(basket); // If filtering, only export filtered notes, inform to the user: // TODO: Filtering tags too!! // TODO: Make sure only filtered notes are exported! // if (decoration()->filterData().isFiltering) // stream << // "

" << i18n("Notes matching the filter "%1":", Tools::textToHTMLWithoutP(decoration()->filterData().string)) << "

\n"; stream << "
\n"; if (basket->isColumnsLayout()) stream << " \n" " \n"; else stream << "
sceneRect().height() << "px; width: " << basket->sceneRect().width() << "px; min-width: 100%;\">\n"; for (Note *note = basket->firstNote(); note; note = note->next()) exportNote(note, /*indent=*/(basket->isFreeLayout() ? 4 : 5)); // Output the footer: if (basket->isColumnsLayout()) stream << "
\n" "
\n"; else stream << "
\n"; stream << QString( " \n" "

%1

\n") .arg(i18n("Made with %2 %3, a tool to take notes and keep information at hand.", KAboutData::applicationData().homepage(), QGuiApplication::applicationDisplayName(), VERSION)); stream << " \n" "\n"; file.close(); stream.setDevice(0); dialog->setValue(dialog->value() + 1); // Basket exportation finished // Recursively export child baskets: BasketListViewItem *item = Global::bnpView->listViewItemForBasket(basket); if (item->childCount() >= 0) { for (int i = 0; i < item->childCount(); i++) { exportBasket(((BasketListViewItem *)item->child(i))->basket(), /*isSubBasket=*/true); } } } void HTMLExporter::exportNote(Note *note, int indent) { QString spaces; if (note->isColumn()) { QString width = ""; if (false /*TODO: DEBUG AND REENABLE: hasResizer()*/) { // As we cannot be precise in CSS (say eg. "width: 50%-40px;"), // we output a percentage that is approximately correct. // For instance, we compute the currently used percentage of width in the basket // and try make make it the same on a 1024*768 display in a Web browser: int availableSpaceForColumnsInThisBasket = note->basket()->sceneRect().width() - (note->basket()->columnsCount() - 1) * Note::RESIZER_WIDTH; int availableSpaceForColumnsInBrowser = 1024 /* typical screen width */ - 25 /* window border and scrollbar width */ - 2 * 5 /* page margin */ - (note->basket()->columnsCount() - 1) * Note::RESIZER_WIDTH; if (availableSpaceForColumnsInThisBasket <= 0) availableSpaceForColumnsInThisBasket = 1; int widthValue = (int)(availableSpaceForColumnsInBrowser * (double)note->groupWidth() / availableSpaceForColumnsInThisBasket); if (widthValue <= 0) widthValue = 1; if (widthValue > 100) widthValue = 100; width = QString(" width=\"%1%\"").arg(QString::number(widthValue)); } stream << spaces.fill(' ', indent) << "\n"; // Export child notes: for (Note *child = note->firstChild(); child; child = child->next()) { stream << spaces.fill(' ', indent + 1); exportNote(child, indent + 1); stream << '\n'; } stream << spaces.fill(' ', indent) << "\n"; if (note->hasResizer()) stream << spaces.fill(' ', indent) << "\n"; return; } QString freeStyle; if (note->isFree()) freeStyle = " style=\"position: absolute; left: " + QString::number(note->x()) + "px; top: " + QString::number(note->y()) + "px; width: " + QString::number(note->groupWidth()) + "px\""; if (note->isGroup()) { stream << '\n' << spaces.fill(' ', indent) << "\n"; // Note content is expected to be on the same HTML line, but NOT groups int i = 0; for (Note *child = note->firstChild(); child; child = child->next()) { stream << spaces.fill(' ', indent); if (i == 0) stream << " isFolded() ? "expand_group_" : "fold_group_") << backgroundColorName << ".png" << "\" width=\"" << Note::EXPANDER_WIDTH << "\" height=\"" << Note::EXPANDER_HEIGHT << "\">\n"; else if (i == 1) stream << " countDirectChilds() << "\">\n"; else stream << " \n"; stream << spaces.fill(' ', indent) << " "; exportNote(child, indent + 3); stream << "\n" << spaces.fill(' ', indent) << " \n"; ++i; } stream << '\n' << spaces.fill(' ', indent) << "\n" /*<< spaces.fill(' ', indent - 1)*/; } else { // Additional class for the content (link, netword, color...): QString additionalClasses = note->content()->cssClass(); if (!additionalClasses.isEmpty()) additionalClasses = ' ' + additionalClasses; // Assign the style of each associated tags: for (State::List::Iterator it = note->states().begin(); it != note->states().end(); ++it) additionalClasses += " tag_" + (*it)->id(); // stream << spaces.fill(' ', indent); stream << ""; if (note->emblemsCount() > 0) { stream << ""; } stream << "
"; for (State::List::Iterator it = note->states().begin(); it != note->states().end(); ++it) if (!(*it)->emblem().isEmpty()) { int emblemSize = 16; QString iconFileName = copyIcon((*it)->emblem(), emblemSize); stream << "\""textEquivalent() << "\" title=\"" << (*it)->fullName() << "\">"; } stream << ""; note->content()->exportToHTML(this, indent); stream << "
"; } } void HTMLExporter::writeBasketTree(BasketScene *currentBasket) { stream << "
    \n"; writeBasketTree(currentBasket, exportedBasket, 3); stream << "
\n"; } void HTMLExporter::writeBasketTree(BasketScene *currentBasket, BasketScene *basket, int indent) { // Compute variable HTML code: QString spaces; QString cssClass = (basket == currentBasket ? " class=\"current\"" : ""); QString link('#'); if (currentBasket != basket) { if (currentBasket == exportedBasket) { link = basketsFolderName + basket->folderName().left(basket->folderName().length() - 1) + ".html"; } else if (basket == exportedBasket) { link = "../../" + fileName; } else { link = basket->folderName().left(basket->folderName().length() - 1) + ".html"; } } QString spanStyle = ""; if (basket->backgroundColorSetting().isValid() || basket->textColorSetting().isValid()) { spanStyle = " style=\"background-color: " + basket->backgroundColor().name() + "; color: " + basket->textColor().name() + "\""; } // Write the basket tree line: stream << spaces.fill(' ', indent) << "
  • " "basketName()) << "\">" "icon(), 16) << "\" width=\"16\" height=\"16\" alt=\"\">" << Tools::textToHTMLWithoutP(basket->basketName()) << ""; // Write the sub-baskets lines & end the current one: BasketListViewItem *item = Global::bnpView->listViewItemForBasket(basket); if (item->childCount() >= 0) { stream << "\n" << spaces.fill(' ', indent) << "
      \n"; for (int i = 0; i < item->childCount(); i++) writeBasketTree(currentBasket, ((BasketListViewItem *)item->child(i))->basket(), indent + 2); stream << spaces.fill(' ', indent) << "
    \n" << spaces.fill(' ', indent) << "
  • \n"; } else { stream << "\n"; } } /** Save an icon to a folder. * If an icon with the same name already exist in the destination, * it is assumed the icon is already copied, so no action is took. * It is optimized so that you can have an empty folder receiving the icons * and call copyIcon() each time you encounter one during export process. */ QString HTMLExporter::copyIcon(const QString &iconName, int size) { if (iconName.isEmpty()) return ""; // Sometimes icon can be "favicons/www.kde.org", we replace the '/' with a '_' QString fileName = iconName; // QString::replace() isn't const, so I must copy the string before fileName = "ico" + QString::number(size) + '_' + fileName.replace('/', '_') + ".png"; QString fullPath = iconsFolderPath + fileName; if (!QFile::exists(fullPath)) DesktopIcon(iconName, size).save(fullPath, "PNG"); return fileName; } /** Done: Sometimes we can call two times copyFile() with the same srcPath and dataFolderPath * (eg. when exporting basket to HTML with two links to same filename * (but not necesary same path, as in "/home/foo.txt" and "/foo.txt") ) * The first copy isn't yet started, so the dest file isn't created and this method * returns the same filename !!!!!!!!!!!!!!!!!!!! */ QString HTMLExporter::copyFile(const QString &srcPath, bool createIt) { QString fileName = Tools::fileNameForNewFile(QUrl::fromLocalFile(srcPath).fileName(), dataFolderPath); QString fullPath = dataFolderPath + fileName; if (!currentBasket->isEncrypted()) { if (createIt) { // We create the file to be sure another very near call to copyFile() willn't choose the same name: QFile file(QUrl::fromLocalFile(fullPath).path()); if (file.open(QIODevice::WriteOnly)) file.close(); // And then we copy the file AND overwriting the file we juste created: KIO::file_copy(QUrl::fromLocalFile(srcPath), QUrl::fromLocalFile(fullPath), 0666, KIO::HideProgressInfo | KIO::Resume | KIO::Overwrite); } else { /*KIO::CopyJob *copyJob = */ KIO::copy(QUrl::fromLocalFile(srcPath), QUrl::fromLocalFile(fullPath), KIO::DefaultFlags); // Do it as before } } else { QByteArray array; bool success = currentBasket->loadFromFile(srcPath, &array); if (success) { saveToFile(fullPath, array); } else { qDebug() << "Unable to load encrypted file " << srcPath; } } return fileName; } void HTMLExporter::saveToFile(const QString &fullPath, const QByteArray &array) { QFile file(QUrl::fromLocalFile(fullPath).path()); if (file.open(QIODevice::WriteOnly)) { file.write(array, array.size()); file.close(); } else { qDebug() << "Unable to open file for writing: " << fullPath; } } diff --git a/src/htmlexporter.h b/src/htmlexporter.h index 8d2d415..0ad2f6f 100644 --- a/src/htmlexporter.h +++ b/src/htmlexporter.h @@ -1,81 +1,68 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef HTMLEXPORTER_H #define HTMLEXPORTER_H #include #include class QProgressDialog; class BasketScene; class Note; /** * @author Sébastien Laoût */ class HTMLExporter { public: explicit HTMLExporter(BasketScene *basket); ~HTMLExporter(); private: void prepareExport(BasketScene *basket, const QString &fullPath); void exportBasket(BasketScene *basket, bool isSubBasket); void exportNote(Note *note, int indent); void writeBasketTree(BasketScene *currentBasket); void writeBasketTree(BasketScene *currentBasket, BasketScene *basket, int indent); void saveToFile(const QString &fullPath, const QByteArray &array); public: QString copyIcon(const QString &iconName, int size); QString copyFile(const QString &srcPath, bool createIt); public: // TODO: make private? // Absolute path of the file name the user chosen: QString filePath; // eg.: "/home/seb/foo.html" QString fileName; // eg.: "foo.html" // Absolute & relative paths for the current basket to be exported: QString basketFilePath; // eg.: "/home/seb/foo.html" or "/home/seb/foo.html_files/baskets/basketN.html" QString filesFolderPath; // eg.: "/home/seb/foo.html_files/" QString filesFolderName; // eg.: "foo.html_files/" or "../" QString iconsFolderPath; // eg.: "/home/seb/foo.html_files/icons/" QString iconsFolderName; // eg.: "foo.html_files/icons/" or "../icons/" QString imagesFolderPath; // eg.: "/home/seb/foo.html_files/images/" QString imagesFolderName; // eg.: "foo.html_files/images/" or "../images/" QString dataFolderPath; // eg.: "/home/seb/foo.html_files/data/" or "/home/seb/foo.html_files/baskets/basketN-data/" QString dataFolderName; // eg.: "foo.html_files/data/" or "basketN-data/" QString basketsFolderPath; // eg.: "/home/seb/foo.html_files/baskets/" QString basketsFolderName; // eg.: "foo.html_files/baskets/" or "" // Various properties of the currently exporting basket: QString backgroundColorName; // Variables used by every export methods: QTextStream stream; BasketScene *exportedBasket; BasketScene *currentBasket; bool withBasketTree; QScopedPointer dialog; }; #endif // HTMLEXPORTER_H diff --git a/src/icon_names.h b/src/icon_names.h index 882e4bf..6862b3a 100644 --- a/src/icon_names.h +++ b/src/icon_names.h @@ -1,35 +1,40 @@ +/** + * SPDX-FileCopyrightText: (C) 2013 Gleb Baryshev + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef ICON_NAMES_H #define ICON_NAMES_H #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-variable" /** @namespace IconNames * @brief Icon names for KIconLoader used by BasKet */ namespace IconNames { #define StrRes static const char *const StrRes LOADING = "process-idle"; StrRes LOCKED = "object-locked"; // Insert stuff StrRes LINK = "insert-link"; StrRes CROSS_REF = LINK; StrRes IMAGE = "insert-image"; StrRes COLOR = "fill-color"; StrRes LAUNCH = "system-run"; StrRes KMENU = "kde"; StrRes ICONS = "preferences-desktop-icons"; StrRes DOCUMENT_IMPORT = "document-import"; // Import from StrRes TOMBOY = "tomboy"; #undef StrRes } #pragma GCC diagnostic pop #endif // ICON_NAMES_H diff --git a/src/kcm_basket.cpp b/src/kcm_basket.cpp index f397450..aa75136 100644 --- a/src/kcm_basket.cpp +++ b/src/kcm_basket.cpp @@ -1,76 +1,62 @@ -/*************************************************************************** - * Copyright (C) 2006 by Petri Damsten * - * damu@iki.fi * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2006 Petri Damsten + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ // This must be first #include "settings.h" //For GeneralPage, etc. #include "settings_versionsync.h" #include #include //---------------------------- // KCM stuff //---------------------------- extern "C" { Q_DECL_EXPORT KCModule *create_basket_config_general(QWidget *parent, const char *) { GeneralPage *page = new GeneralPage(parent, "kcmbasket_config_general"); return page; } } extern "C" { Q_DECL_EXPORT KCModule *create_basket_config_baskets(QWidget *parent, const char *) { BasketsPage *page = new BasketsPage(parent, "kcmbasket_config_baskets"); return page; } } extern "C" { Q_DECL_EXPORT KCModule *create_basket_config_new_notes(QWidget *parent, const char *) { NewNotesPage *page = new NewNotesPage(parent, "kcmbasket_config_new_notes"); return page; } } extern "C" { Q_DECL_EXPORT KCModule *create_basket_config_notes_appearance(QWidget *parent, const char *) { NotesAppearancePage *page = new NotesAppearancePage(parent, "kcmbasket_config_notes_appearance"); return page; } } extern "C" { Q_DECL_EXPORT KCModule *create_basket_config_apps(QWidget *parent, const char *) { ApplicationsPage *page = new ApplicationsPage(parent, "kcmbasket_config_apps"); return page; } } extern "C" { Q_DECL_EXPORT KCModule *create_basket_config_version_sync(QWidget *parent, const char *) { VersionSyncPage *page = new VersionSyncPage(parent, "kcmbasket_config_version_sync"); return page; } } diff --git a/src/kcolorcombo2.cpp b/src/kcolorcombo2.cpp index af71c74..090e0de 100644 --- a/src/kcolorcombo2.cpp +++ b/src/kcolorcombo2.cpp @@ -1,767 +1,754 @@ -/*************************************************************************** - * Copyright (C) 2005 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2005 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "kcolorcombo2.h" #ifndef USE_OLD_KCOLORCOMBO #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define DEBUG_COLOR_ARRAY //#define OUTPUT_GIMP_PALETTE /** class KColorPopup: */ const int KColorPopup::MARGIN = 1; const int KColorPopup::FRAME_WIDTH = 1; KColorPopup::KColorPopup(KColorCombo2 *parent) : QWidget(/*parent=*/0, Qt::Popup) , m_selector(parent) , m_pixmap(0) { hide(); setMouseTracking(true); // resize(20, 20); } KColorPopup::~KColorPopup() { delete m_pixmap; } #include void KColorPopup::relayout() // FIXME: relayout should NOT redraw the pixmap! { int columnCount = m_selector->columnCount(); int rowCount = m_selector->rowCount(); int colorHeight = m_selector->colorRectHeight(); int colorWidth = m_selector->colorRectWidthForHeight(colorHeight); bool haveDefault = m_selector->defaultColor().isValid(); int width = 2 + MARGIN + (colorWidth + MARGIN) * columnCount; int height = 2 + MARGIN + (colorHeight + MARGIN) * rowCount + (colorHeight + MARGIN); resize(width, height); // Initialize the pixmap: delete m_pixmap; m_pixmap = new QPixmap(width, height); QPainter painter(m_pixmap); painter.fillRect(0, 0, width, height, palette().color(QPalette::Base)); painter.setPen(palette().color(QPalette::Text)); painter.drawRect(0, 0, width, height); // Needed to draw: int x, y; QRect selectionRect; // Draw the color array: for (int i = 0; i < columnCount; ++i) { for (int j = 0; j < rowCount; ++j) { x = 1 + MARGIN + (colorWidth + MARGIN) * i; y = 1 + MARGIN + (colorHeight + MARGIN) * j; if (i == m_selectedColumn && j == m_selectedRow) { selectionRect = QRect(x - 2, y - 2, colorWidth + 4, colorHeight + 4); painter.fillRect(selectionRect, palette().color(QPalette::Highlight)); } m_selector->drawColorRect(painter, x, y, m_selector->colorAt(i, j), /*isDefault=*/false, colorWidth, colorHeight); } } m_columnOther = (haveDefault ? columnCount / 2 : 0); // "(Default)" is allowed, paint "Other..." on the right int defaultCellWidth = (colorWidth + MARGIN) * m_columnOther; int otherCellWidth = (colorWidth + MARGIN) * (columnCount - m_columnOther); // Draw the "(Default)" and "Other..." colors: y = height - (colorHeight + MARGIN) - 1; QColor textColor; if (m_selector->defaultColor().isValid()) { x = 1 + MARGIN; if (m_selectedColumn < m_columnOther && rowCount == m_selectedRow) { selectionRect = QRect(x - 2, y - 2, defaultCellWidth, colorHeight + 4); painter.fillRect(selectionRect, palette().color(QPalette::Highlight)); textColor = palette().color(QPalette::HighlightedText); } else textColor = palette().color(QPalette::Text); m_selector->drawColorRect(painter, x, y, m_selector->defaultColor(), /*isDefault=*/true, colorWidth, colorHeight); painter.setFont(m_selector->font()); painter.setPen(textColor); painter.drawText(x + 2 + colorWidth, y, /*width=*/5000, colorHeight, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip, i18n("(Default)")); } x = 1 + MARGIN + m_columnOther * (colorWidth + MARGIN); if (m_selectedColumn >= m_columnOther && rowCount == m_selectedRow) { selectionRect = QRect(x - 2, y - 2, otherCellWidth, colorHeight + 4); painter.fillRect(selectionRect, palette().color(QPalette::Highlight)); textColor = palette().color(QPalette::HighlightedText); } else textColor = palette().color(QPalette::Text); m_selector->drawColorRect(painter, x, y, m_otherColor, /*isDefault=*/false, colorWidth, colorHeight); painter.setFont(m_selector->font()); painter.setPen(textColor); painter.drawText(x + 2 + colorWidth, y, /*width=*/5000, colorHeight, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip, i18n("Other...")); // QPoint pos = mapFromGlobal(QCursor::pos()); // painter.drawRect(pos.x(), pos.y(), 5000, 5000); } void KColorPopup::updateCell(int column, int row) { int colorHeight = m_selector->colorRectHeight(); int colorWidth = m_selector->colorRectWidthForHeight(colorHeight); int x = 1 + MARGIN + -2 + column * (colorWidth + MARGIN); int y = 1 + MARGIN + -2 + row * (colorHeight + MARGIN); int width = colorWidth + MARGIN; int height = colorHeight + MARGIN; if (row == m_selector->rowCount()) { if (m_selectedColumn < m_columnOther) // The "(Default)" cell: width = (colorWidth + MARGIN) * m_columnOther; else // The "Other..." cell: width = (colorWidth + MARGIN) * (m_selector->columnCount() - m_columnOther); } update(x, y, width, height); } void KColorPopup::doSelection() { m_otherColor = QColor(); // If the selected color is not the default one, try to find it in the array: if (m_selector->color().isValid()) { bool isInArray = false; for (int column = 0; column < m_selector->columnCount(); ++column) for (int row = 0; row < m_selector->rowCount(); ++row) if (m_selector->color() == m_selector->colorAt(column, row)) { m_selectedColumn = column; m_selectedRow = row; isInArray = true; } // If not found in array, it's another one: if (!isInArray) { m_selectedColumn = m_columnOther; m_selectedRow = m_selector->rowCount(); m_otherColor = m_selector->color(); } // If it's the default one: } else { m_selectedColumn = 0; m_selectedRow = m_selector->rowCount(); } } void KColorPopup::validate() { hide(); close(); emit closed(); if (m_selectedRow != m_selector->rowCount()) // A normal row: m_selector->setColor(m_selector->colorAt(m_selectedColumn, m_selectedRow)); else if (m_selectedColumn < m_columnOther) // The default color: m_selector->setColor(QColor()); else { // The user want to choose one: QColor color = m_selector->effectiveColor(); color = QColorDialog::getColor(color, this); if (color.isValid()) m_selector->setColor(color); } } void KColorPopup::mousePressEvent(QMouseEvent *event) { int x = event->pos().x(); int y = event->pos().y(); if (x < 0 || y < 0 || x >= width() || y >= height()) { hide(); close(); emit closed(); } else validate(); event->accept(); } void KColorPopup::paintEvent(QPaintEvent *event) { QPainter painter(this); if (m_pixmap) painter.drawPixmap(0, 0, *m_pixmap); painter.setPen(Qt::black); painter.drawRect(event->rect()); } void KColorPopup::mouseMoveEvent(QMouseEvent *event) { int x = event->pos().x(); int y = event->pos().y(); if (x < FRAME_WIDTH + 2 || y < FRAME_WIDTH + 2 || x > width() - 2 - 2 * FRAME_WIDTH || y > height() - 2 - 2 * FRAME_WIDTH) return; int colorHeight = m_selector->colorRectHeight(); int colorWidth = m_selector->colorRectWidthForHeight(colorHeight); // int oldSelectedColumn = m_selectedColumn; // int oldSelectedRow = m_selectedRow; m_selectedColumn = (x - FRAME_WIDTH - MARGIN + 2) / (colorWidth + MARGIN); m_selectedRow = (y - FRAME_WIDTH - MARGIN + 2) / (colorHeight + MARGIN); relayout(); update(); } void KColorPopup::keyPressEvent(QKeyEvent *event) { int column = m_selectedColumn; int row = m_selectedRow; int columnCount = m_selector->columnCount(); int rowCount = m_selector->rowCount(); switch (event->key()) { case Qt::Key_Right: if (m_selectedRow != rowCount) // A normal row: column = (column + 1) % columnCount; else { // The last row, if there are two choices, switch. Else, do nothing: if (m_selector->defaultColor().isValid()) column = (m_selectedColumn < m_columnOther ? m_columnOther : 0); } break; case Qt::Key_Left: if (m_selectedRow != rowCount) { // A normal row: column = (column - 1); if (column < 0) column = columnCount - 1; } else { // The last row, if there are two choices, switch. Else, do nothing: if (m_selector->defaultColor().isValid()) column = (m_selectedColumn < m_columnOther ? m_columnOther : 0); } break; case Qt::Key_Up: row = (row - 1); if (row < 0) row = rowCount; break; case Qt::Key_Down: row = (row + 1) % (rowCount + 1); break; case Qt::Key_PageDown: row += 10; if (row > rowCount) row = rowCount; break; case Qt::Key_PageUp: row -= 10; if (row < 0) row = 0; break; case Qt::Key_Home: row = 0; column = 0; break; case Qt::Key_End: row = rowCount; column = columnCount - 1; break; case Qt::Key_Return: validate(); break; default: QWidget::keyPressEvent(event); } if (row != m_selectedRow || column != m_selectedColumn) { m_selectedRow = row; m_selectedColumn = column; relayout(); update(); } } /** Helper function: */ QColor Tool_mixColors(const QColor &color1, const QColor &color2) { QColor mixedColor; mixedColor.setRgb((color1.red() + color2.red()) / 2, (color1.green() + color2.green()) / 2, (color1.blue() + color2.blue()) / 2); return mixedColor; } /** class KColorCombo2Private */ class KColorCombo2::KColorCombo2Private { }; /** class KColorCombo2: */ /* All code for the popup management (including the constructor, popup() and eventFilter()) * has been copied from the KDateEdit widget (in libkdepim). * * Some other piece of code comes from KColorButton (in libkdeui) to enable color drag, drop, copy and paste. */ KColorCombo2::KColorCombo2(const QColor &color, const QColor &defaultColor, QWidget *parent) : KComboBox(parent) , m_color(color) , m_defaultColor(defaultColor) { setEditable(false); init(); } KColorCombo2::KColorCombo2(const QColor &color, QWidget *parent) : KComboBox(parent) , m_color(color) , m_defaultColor() { setEditable(false); init(); } void KColorCombo2::init() { m_colorArray = 0; d = new KColorCombo2Private(); setDefaultColor(m_defaultColor); insertItem(/*index=*/0, ""); updateComboBox(); // It need an item of index 0 to exists, so we created it. setAcceptDrops(true); m_popup = new KColorPopup(this); m_popup->installEventFilter(this); connect(m_popup, SIGNAL(closed()), SLOT(popupClosed())); // By default, the array is filled with setRainbowPreset(). // But we allocate it on demand (the later as possible) to avoid performances issues if the developer set another array. // However, to keep columnCount() rowCount() const, we define theme here: m_columnCount = 13; m_rowCount = 9; } KColorCombo2::~KColorCombo2() { deleteColorArray(); } void KColorCombo2::setColor(const QColor &color) { // Do nothing if the color should be set to the default one and there is no such default color allowed: if (!color.isValid() && !m_defaultColor.isValid()) { // kdebug << this::FUNCTION << "Trying to assign the default color (an invalid one) whereas no such default color is allowed"; return; } if (m_color != color) { m_color = color; updateComboBox(); emit changed(color); } } QColor KColorCombo2::color() const { return m_color; } QColor KColorCombo2::effectiveColor() const { if (m_color.isValid()) return m_color; else return m_defaultColor; } void KColorCombo2::setRainbowPreset(int colorColumnCount, int lightRowCount, int darkRowCount, bool withGray) { // At least one row and one column: if (colorColumnCount < 1 - (withGray ? 1 : 0)) colorColumnCount = 1 - (withGray ? 1 : 0); if (lightRowCount < 0) lightRowCount = 0; if (darkRowCount < 0) darkRowCount = 0; // Create the array: int columnCount = colorColumnCount + (withGray ? 1 : 0); int rowCount = lightRowCount + 1 + darkRowCount; newColorArray(columnCount, rowCount); // Fill the array: for (int i = 0; i < colorColumnCount; ++i) { int hue = i * 360 / colorColumnCount; // With light colors: for (int j = 1; j <= lightRowCount; ++j) { // Start to 1 because we don't want a row full of white! int saturation = j * 255 / (lightRowCount + 1); setColorAt(i, j - 1, QColor::fromHsv(hue, saturation, 255)); } // With pure colors: setColorAt(i, lightRowCount, QColor::fromHsv(hue, 255, 255)); // With dark colors: for (int j = 1; j <= darkRowCount; ++j) { int value = 255 - j * 255 / (darkRowCount + 1); setColorAt(i, lightRowCount + j, QColor::fromHsv(hue, 255, value)); } } // Fill the gray column: if (withGray) { for (int i = 0; i < rowCount; ++i) { int gray = (rowCount == 1 ? 128 : 255 - (i * 255 / (rowCount - 1))); setColorAt(columnCount - 1, i, QColor(gray, gray, gray)); } } #ifdef DEBUG_COLOR_ARRAY qDebug() << "KColorCombo2::setColorPreset"; for (int j = 0; j < rowCount; ++j) { for (int i = 0; i < columnCount; ++i) { int h, s, v; m_colorArray[i][j].getHsv(&h, &s, &v); qDebug() << QString("(%1,%2,%3)").arg(h, 3).arg(s, 3).arg(v, 3); // qDebug() << colorArray[i][j].name() << " "; } qDebug(); } #endif #ifdef OUTPUT_GIMP_PALETTE qDebug() << "GIMP Palette"; for (int j = 0; j < rowCount; ++j) { for (int i = 0; i < columnCount; ++i) { qDebug() << QString("(%1,%2,%3)").arg(m_colorArray[i][j].red(), 3).arg(m_colorArray[i][j].green(), 3).arg(m_colorArray[i][j].blue(), 3); } } #endif } int KColorCombo2::columnCount() const { return m_columnCount; } int KColorCombo2::rowCount() const { return m_rowCount; } QColor KColorCombo2::colorAt(int column, int row) /* const*/ { if (!m_colorArray) setRainbowPreset(); if (column < 0 || row < 0 || column >= m_columnCount || row >= m_rowCount) return QColor(); return m_colorArray[column][row]; } QColor KColorCombo2::defaultColor() const { return m_defaultColor; } void KColorCombo2::newColorArray(int columnCount, int rowCount) { if (columnCount <= 0 || rowCount <= 0) { // kdebug << this::FUNCTION << "Trying to create an empty new color array (with %d columns and %d rows)"; return; } // Delete any previous array (if any): deleteColorArray(); // Create a new array of the wanted dimensions: m_columnCount = columnCount; m_rowCount = rowCount; m_colorArray = new QColor *[columnCount]; for (int i = 0; i < columnCount; ++i) m_colorArray[i] = new QColor[rowCount]; } void KColorCombo2::setColorAt(int column, int row, const QColor &color) { if (!m_colorArray) setRainbowPreset(); if (column < 0 || row < 0 || column >= m_columnCount || row >= m_rowCount) { // kdebug << this::FUNCTION << "Trying to set a color at an invalid index (at column %d and row %d, whereas the array have %d columns and %d rows)"; return; } m_colorArray[column][row] = color; } void KColorCombo2::setDefaultColor(const QColor &color) { m_defaultColor = color; if (!m_defaultColor.isValid() && !m_color.isValid()) m_color = Qt::white; // FIXME: Use the first one. } QPixmap KColorCombo2::colorRectPixmap(const QColor &color, bool isDefault, int width, int height) { // Prepare to draw: QPixmap pixmap(width, height); QBitmap mask(width, height); QPainter painter(&pixmap); QPainter maskPainter(&mask); // Draw pixmap: drawColorRect(painter, 0, 0, color, isDefault, width, height); // Draw mask (make the four corners transparent): maskPainter.fillRect(0, 0, width, height, Qt::color1); // opaque maskPainter.setPen(Qt::color0); // transparent maskPainter.drawPoint(0, 0); maskPainter.drawPoint(0, height - 1); maskPainter.drawPoint(width - 1, height - 1); maskPainter.drawPoint(width - 1, 0); // Finish: painter.end(); maskPainter.end(); pixmap.setMask(mask); return pixmap; } void KColorCombo2::drawColorRect(QPainter &painter, int x, int y, const QColor &color, bool isDefault, int width, int height) { // Fill: if (color.isValid()) painter.fillRect(x /*+ 1*/, y /*+ 1*/, width /*- 2*/, height /*- 2*/, color); else { // If it's an invalid color, it's for the "Other..." entry: draw a rainbow. // If it wasn't for the "Other..." entry, the programmer made a fault, so (s)he will be informed about that visually. for (int i = 0; i < width - 2; ++i) { int hue = i * 360 / (width - 2); for (int j = 0; j < height - 2; ++j) { int saturation = 255 - (j * 255 / (height - 2)); painter.setPen(QColor::fromHsv(hue, saturation, /*value=*/255)); painter.drawPoint(x + i + 1, y + j + 1); } } } // Stroke: int dontCare, value; color.getHsv(/*hue:*/ &dontCare, /*saturation:*/ &dontCare, &value); QColor stroke = (color.isValid() ? color.dark(125) : palette().color(QPalette::Text)); painter.setPen(/*color);//*/ stroke); painter.drawLine(x + 1, y, x + width - 2, y); painter.drawLine(x, y + 1, x, y + height - 2); painter.drawLine(x + 1, y + height - 1, x + width - 2, y + height - 1); painter.drawLine(x + width - 1, y + 1, x + width - 1, y + height - 2); // Round corners: QColor antialiasing; if (color.isValid()) { antialiasing = Tool_mixColors(color, stroke); painter.setPen(antialiasing); painter.drawPoint(x + 1, y + 1); painter.drawPoint(x + 1, y + height - 2); painter.drawPoint(x + width - 2, y + height - 2); painter.drawPoint(x + width - 2, y + 1); } else { // The two top corners: antialiasing = Tool_mixColors(Qt::red, stroke); painter.setPen(antialiasing); painter.drawPoint(x + 1, y + 1); painter.drawPoint(x + width - 2, y + 1); // The two bottom ones: antialiasing = Tool_mixColors(Qt::white, stroke); painter.setPen(antialiasing); painter.drawPoint(x + 1, y + height - 2); painter.drawPoint(x + width - 2, y + height - 2); } // Mark default color: if (isDefault) { painter.setPen(stroke); painter.drawLine(x + 1, y + height - 2, x + width - 2, y + 1); } } int KColorCombo2::colorRectHeight() const { return (fontMetrics().boundingRect(i18n("(Default)")).height() + 2) * 3 / 2; } int KColorCombo2::colorRectWidthForHeight(int height) const { return height * 14 / 10; // 1.4 times the height, like A4 papers. } void KColorCombo2::deleteColorArray() { if (m_colorArray) { for (int i = 0; i < m_columnCount; ++i) delete[] m_colorArray[i]; delete[] m_colorArray; m_colorArray = 0; } } void KColorCombo2::updateComboBox() { int height = colorRectHeight() * 2 / 3; // fontMetrics().boundingRect(i18n("(Default)")).height() + 2 QPixmap pixmap = colorRectPixmap(effectiveColor(), !m_color.isValid(), height, height); // TODO: isDefaultColorSelected() setItemIcon(/*index=*/0, pixmap); setItemText(/*index=*/0, (m_color.isValid() ? QString(i18n("R:%1, G:%2, B:%3", m_color.red(), m_color.green(), m_color.blue())) : i18nc("color", "(Default)"))); } void KColorCombo2::showPopup() { if (!m_colorArray) setRainbowPreset(); // Compute where to show the popup: QRect desk = qApp->desktop()->screenGeometry(this); QPoint popupPoint = mapToGlobal(QPoint(0, 0)); int popupHeight = m_popup->size().height(); if (popupPoint.y() + height() + popupHeight > desk.bottom()) popupPoint.setY(popupPoint.y() - popupHeight); else popupPoint.setY(popupPoint.y() + height()); int popupWidth = m_popup->size().width(); if (popupPoint.x() + popupWidth > desk.right()) popupPoint.setX(desk.right() - popupWidth); if (popupPoint.x() < desk.left()) popupPoint.setX(desk.left()); if (popupPoint.y() < desk.top()) popupPoint.setY(desk.top()); // Configure the popup: m_popup->move(popupPoint); // m_popup->setColor(m_color); m_popup->doSelection(); m_popup->relayout(); // FIXME: In aboutToShow() ? #if 0 //#ifndef QT_NO_EFFECTS if (QApplication::isEffectEnabled(UI_AnimateCombo)) { if (m_popup->y() < mapToGlobal(QPoint(0, 0)).y()) qScrollEffect(m_popup, QEffects::UpScroll); else qScrollEffect(m_popup); } else #endif m_popup->show(); // The combo box is now shown pressed. Make it show not pressed again // by causing its (invisible) list box to emit a 'selected' signal. // Simulate an Enter to unpress it: /*QListWidget *lb = listBox(); if (lb) { lb->setCurrentItem(0); QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, 0, 0); QApplication::postEvent(lb, keyEvent); }*/ } void KColorCombo2::mouseMoveEvent(QMouseEvent *event) { if ((event->buttons() & Qt::LeftButton) && (event->pos() - m_dragStartPos).manhattanLength() > qApp->startDragDistance()) { // Drag color object: QMimeData *mimeData = new QMimeData; QDrag *colorDrag = new QDrag(this); mimeData->setColorData(effectiveColor()); // Replace the drag pixmap with our own rounded one, at the same position and dimensions: QPixmap pixmap = colorDrag->pixmap(); pixmap = colorRectPixmap(effectiveColor(), /*isDefault=*/false, pixmap.width(), pixmap.height()); colorDrag->setPixmap(pixmap); colorDrag->setHotSpot(colorDrag->hotSpot()); colorDrag->exec(Qt::CopyAction, Qt::CopyAction); // setDown(false); } } void KColorCombo2::dragEnterEvent(QDragEnterEvent *event) { if (isEnabled() && event->mimeData()->hasColor()) event->accept(); } void KColorCombo2::dropEvent(QDropEvent *event) { QColor color; color = qvariant_cast(event->mimeData()->colorData()); if (color.isValid()) setColor(color); } void KColorCombo2::keyPressEvent(QKeyEvent *event) { QKeySequence key(event->key()); if (KStandardShortcut::copy().contains(key)) { QMimeData *mime = new QMimeData; mime->setColorData(effectiveColor()); QApplication::clipboard()->setMimeData(mime, QClipboard::Clipboard); } else if (KStandardShortcut::paste().contains(key)) { QColor color; color = qvariant_cast(QApplication::clipboard()->mimeData(QClipboard::Clipboard)->colorData()); setColor(color); } else KComboBox::keyPressEvent(event); } void KColorCombo2::fontChange(const QFont &) { // Since the color-rectangle is the same height of the text, we should resize it if the font change: updateComboBox(); } void KColorCombo2::virtual_hook(int /*id*/, void * /*data*/) { /* KBASE::virtual_hook(id, data); */ } void KColorCombo2::popupClosed() { hidePopup(); } #endif // USE_OLD_KCOLORCOMBO diff --git a/src/kcolorcombo2.h b/src/kcolorcombo2.h index ecdfe45..0264c30 100644 --- a/src/kcolorcombo2.h +++ b/src/kcolorcombo2.h @@ -1,346 +1,333 @@ -/*************************************************************************** - * Copyright (C) 2005 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2005 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef KCOLORCOMBO2_H #define KCOLORCOMBO2_H #include class QColor; class QPixmap; class QDragEnterEvent; class QDropEvent; class QMouseEvent; class QKeyEvent; class QPaintEvent; class KColorPopup; /** * @short A combobox to display or allow user selection of a color in a user-friendly way. * * A combobox widget that popup an array of colors for the user to easily pick a common color.\n * He/she can use the popup to quickly pick a reasonable color or open a color chooser dialog for a more precise choice.\n * The user can also choose a default color (the standard background color, text color, etc... it's to the programmer to make sense of this property).\n * \n * The user is also offered some facilities: like KColorButton he/she can copy a color or paste it * (with standard keyboard shortcuts, usually Ctrl+C and Ctrl+V), and he/she can drag or drop colors. * * @par Quick usage: * Just create a new KColorCombo2() with the initial color and eventually an allowed default color * (eg. palette().color(QPalette::Base) for a background color, palette().color(QPalette::Text)...).\n * You will be noticed of the color the user selects with the signal changed(), or you can use color() to get the color at any moment.\n * Note that they can return an invalid color (see QColor::isValid()) if the user chosen the default color (if he can choose that).\n * It's then easy to save in settings, but if you want the real color (even for the default), you can get it with effectiveColor(). * * @par Notes about default color: * If you set a default color using Qt or KDE standard colors, the user can change them in the KDE Control Center, * but this widget willn't be update and will still show the old one.\n * To be noticed of such color change and then update the widget with the new standard color, you can use one of those two methods: * @code * void QWidgetDerivate::paletteChange(const QPalette &oldPalette) { // QWidgetDerivate is a parent or near custom widget * theComboBox->setDefaultColor(theNewDefaultColor); * QWidget::paletteChange(oldPalette); * } * @endcode * or connect the signal QApplication::kdisplayPaletteChanged() to a slot that will set the default color of this widget. * * @par Advanced usage: * By default, the combobox show a well balanced rainbow, OK for most usages, and you don't need to do anything for it to work.\n * You however can set your own color array by calling newColorArray() with the number of columns and rows. * Then, setColorAt() several times to fill the array.\n * This allow the most flexibility. But if you just want a rainbow with more or less colors, setRainbowPreset() is what you want.\n * If you worry about performance issues of creating a combobox with the default color array and then allocating another color array by yourself, * note that the default color array is not allocated in the constructor, but as soon as it is demanded (on first popup if no array has been * set before, or on first call of any accessors: colorAt(), columnCount(), setColorAt()...). * Finally, colorRectPixmap() and drawColorRect() allow to draw the color rounded-rectangle in other places for a consistent look. * * @see KGlobalSettings Use one of the static functions to get KDE standard colors for default values. * @see KColorButton The same, but without the rainbow popup or the choice of a default color. * @see QColorDialog The dialog that is shown when the user click the "Other..." entry. * @author Sébastien Laoût * * @image html commoncolorselector.png "Common Color Selector ComboBox" */ class KColorCombo2 : public KComboBox { Q_OBJECT Q_PROPERTY(QColor color READ color WRITE setColor) Q_PROPERTY(QColor defaultColor READ defaultColor WRITE setDefaultColor) public slots: /** * Change the selected color.\n * If the popup is open, it will not reflect the change. FIXME: Should it? * @param color The new selected color. Can be invalid to select the default one.\n * If @p color is invalid and no default color is allowed, the function will keep the old one. */ void setColor(const QColor &color); /** * Change the default color. * @param color The color to return if the user choose the default one. If it is not valid, the user willn't be allowed to choose a default one. * @see defaultColor() to get it. */ void setDefaultColor(const QColor &color); private slots: void popupClosed(); signals: /** * Emitted when the color of the widget is changed, either with setColor() or via user selection. * @see color() to know the content of @p newColor. */ void changed(const QColor &newColor); public: /** * Constructs a color combobox with parent @p parent. * @param color The initial selected color. If it is not valid, the default one will then be selected.\n * But if @p color is invalid and there is no default color, the result is undefined. * @param defaultColor The color to return if the user choose the default one. If it is not valid, the user willn't be allowed to choose a default one. */ KColorCombo2(const QColor &color, const QColor &defaultColor, QWidget *parent = 0); /** * Constructs a color combobox with parent @p parent.\n * The user is not allowed to choose a default color, unless you call setDefaultColor() later. * @param color The initial selected color. If it is invalid, the result is undefined. */ explicit KColorCombo2(const QColor &color, QWidget *parent = 0L); /** * Destroys the combobox. */ ~KColorCombo2() override; /** * Get the color chosen by the user.\n * Can be invalid, if the user chosen the default one.\n * Ideal to store it in settings for later recall. * @see effectiveColor() if you want the color to be always valid. */ QColor color() const; /** * Return the color chosen by the user.\n * If the user chosen the default color, the default one is then returned, so the returned color is always valid.\n * Ideal to directly use to draw. * @see color() if you want to be notified of a default color choice. */ QColor effectiveColor() const; /** * Returns the default color or an invalid color if no default color is set (if the user isn't allowed to choose a default color). * @see setDefaultColor() to change it. */ QColor defaultColor() const; /** * Allocate a new color array of the specified dimension.\n * The new array will have invalid colors: you should then assign them one by one.\n * If one or both of the dimensions are negative or null, this function do nothing (both dimensions are always ensured to be at least equal to 1). * @param columnCount The number of columns of the array. * @param rowCount The number of rows of the array. * @see setColorAt() to set all colors once the array have been created. */ void newColorArray(int columnCount, int rowCount); /** * Get the number of columns in the array that the user can see to choose. * @see rowCount() for the number of rows, and colorAt() to get a color from the array. */ int columnCount() const; /** * Get the number of rows in the array that the user can see to choose. * @see columnCount() for the number of columns, and colorAt() to get a color from the array. */ int rowCount() const; /** * Set a color in the array at position (column,row).\n * If one or both of the indexes are out of range, this function do nothing.\n * @p column and @p row start from 0 to columnCount()-1 and columnRow()-1. * * @param column The x coordinate of the color to set or change. * @param row The y coordinate of the color to set or change. * @param color The color to assign at this position. */ void setColorAt(int column, int row, const QColor &color); /** * Get a color in the array that the user can see to choose.\n * @p column and @p row start from 0 to columnCount()-1 and columnRow()-1. * * @return The asked color, or an invalid color if the index is out of limit of the array. * @see columnCount() and rowCount() to get the array dimensions. */ QColor colorAt(int column, int row) /* const*/; /** * Fill the array of colors (that will be shown to the user in the popup that appears when he/she click the arrow) with a rainbow of different luminosity.\n * This rainbow representation have the advantage of being natural and well structured for a human to be able to select reasonable colors.\n * This function will allocate a color array by itself depending on the parameters (no need to call newColorArray()). * @param colorColumnCount The number of columns. The 360 possible colors of the rainbow will be split to take the wanted number of colors, equally separated. * @param lightRowCount There is always at least 1 row of colors: the "pure" colors: pure red, pure blue, pure green, pure fuchsia...\n * Additionally, you can add row on top: they will contain the same colors, but lighter.\n * The parameter @p lightRowCount specify how many different lighting grades should be shown (from near to white, but not white, to "pure"). * @param darkRowCount Finally, on bottom of the row of "pure colors", you can put any variety of dark colors (from "pure", to near to black, but not black).\n * So, the number of rows is equal to @p lightRowCount + 1 + @p darkRowCount. On top are light colors, gradually going to dark ones on bottom. * @param withGray If true, another column (so there will be @p colorColumnCount+1 columns) is added on the very-right of the popup * to show different gray values, matching the brightness of the sibling colors. * * The most acceptable parameters: * @li The default values are good to have the 7 colors of the rainbow + colors between them, and light/dark colors are well distinct. * @li If the color is a background color, you can set @p darkRowCount to 0, so only light colors are shown. * @li The inverse is true for text color choice: you can set @p lightRowCount to 0. * @li But be careful: some advanced users prefer white text on dark background, so you eg. can set @p lightRowCount to a big value and * @p darkRowCount to a small one for a fewer choice of dark colors, but at least some ones. */ void setRainbowPreset(int colorColumnCount = 12, int lightRowCount = 4, int darkRowCount = 4, bool withGray = true); // void setHsvPreset(QColor hue[], QColor saturation[], QColor value[], bool withGray = true); /** * Returns a pixmap of a colored rounded-rectangle. The four corners are transparent.\n * Useful if you want to set such a rectangle as an icon for a menu entry, or for drag and drop operation... * @param color The color of the rectangle. If the color is invalid, a rainbow is then drawn (like for the "Other..." entry in the popup). * @param isDefault True if @p color is the default one and should then be draw with a diagonal line. * @param width The width of the rectangle pixmap to return. * @param height The height of the rectangle pixmap to return. * * @see drawColorRect() if you need to draw it directly: it's faster. */ QPixmap colorRectPixmap(const QColor &color, bool isDefault, int width, int height); /** * Draw an image of a colored rounded-rectangle.\n * This is like colorRectPixmap() but significantly faster because there is nothing to copy, and no transparency mask to create and apply. * @param painter The painter where to draw the image. * @param x The x coordinate on the @p painter where to draw the rectangle. * @param y The y coordinate on the @p painter where to draw the rectangle. * @param color The color of the rectangle. If the color is invalid, a rainbow is then drawn (like for the "Other..." entry in the popup). * @param isDefault True if @p color is the default one and should then be draw with a diagonal line. * @param width The width of the rectangle pixmap to return. * @param height The height of the rectangle pixmap to return. * * @see colorRectPixmap() to get a transparent pixmap of the rectangle. */ void drawColorRect(QPainter &painter, int x, int y, const QColor &color, bool isDefault, int width, int height); /** * Get the height of a color rectangle for this combobox.\n * This is equal to the text height, regarding to the current font of this combobox. */ int colorRectHeight() const; /** * Get the width of a color rectangle, depending of the @p height of it.\n * It typically return 1.4 * @p height for decent rectangle proportions. */ int colorRectWidthForHeight(int height) const; protected: void showPopup() override; void mouseMoveEvent(QMouseEvent *event) override; void dragEnterEvent(QDragEnterEvent *event) override; void dropEvent(QDropEvent *event) override; void keyPressEvent(QKeyEvent *event) override; virtual void fontChange(const QFont &oldFont); private: /** * Initialization routine common to every constructors.\n * Constructors just have to initialize the KComboBox, m_color and m_defaultColor * and this function do the rest to complete the creation of this widget. */ void init(); /** * Free up all memory allocated for the color array.\n * But only if an array have previously been allocated, of course. */ void deleteColorArray(); /** * Update the only item of the combobox to mirror the new selected color.\n * Mainly called on init() and setColor(). */ void updateComboBox(); KColorPopup *m_popup; QColor m_color; QColor m_defaultColor; QColor **m_colorArray; int m_columnCount; int m_rowCount; QPoint m_dragStartPos; protected: /** * Keep place for future improvements without having to break binary compatibility.\n * Does nothing for the moment. */ void virtual_hook(int id, void *data) override; private: /** * Keep place for future improvements without having to break binary compatibility. */ class KColorCombo2Private; KColorCombo2Private *d; }; // TODO: setColorArray(QColor **, int, int) and use signals/slots ?? class KColorPopup : public QWidget { Q_OBJECT public: explicit KColorPopup(KColorCombo2 *parent); ~KColorPopup() override; void relayout(); // updateGeometry() ?? signals: void closed(); protected: void paintEvent(QPaintEvent * /*event*/) override; void mouseMoveEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void doSelection(); void validate(); void updateCell(int column, int row); friend class KColorCombo2; private: KColorCombo2 *m_selector; QPixmap *m_pixmap; int m_selectedRow; int m_selectedColumn; int m_columnOther; QColor m_otherColor; static const int MARGIN; static const int FRAME_WIDTH; }; #endif // KCOLORCOMBO2_H diff --git a/src/kde4_migration.h b/src/kde4_migration.h index 9d86806..51001a7 100644 --- a/src/kde4_migration.h +++ b/src/kde4_migration.h @@ -1,158 +1,145 @@ -/* - * Copyright (C) 2015 by Gleb Baryshev +/** + * SPDX-FileCopyrightText: (C) 2015 Gleb Baryshev * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef KDE4_MIGRATION_H #define KDE4_MIGRATION_H #include #include #include #include #include #include #include #include #include #include #include #include #include /** Use Kdelibs4ConfigMigrator to detect KDE4 config and then migrate both config and data. Suggest deleting the files from the old location Prerequisite: existing QApplication in order to run KIO Jobs and QEventLoop */ class Kde4Migrator { public: //! @returns True if both config and data have been migrated bool migrateKde4Data() { Kdelibs4ConfigMigrator rcMigrator(KAboutData::applicationData().componentName()); rcMigrator.setConfigFiles({BASKET_RC}); rcMigrator.setUiFiles({"basketui.rc", "basket_part.rc"}); if (rcMigrator.migrate()) qDebug() << "Kdelibs4ConfigMigrator migrate=true"; else return false; // Safety check // ~/.local/share/basket if (QDir(getNewDataDir()).exists()) { qDebug() << "Directory" << getNewDataDir() << "already exists, not trying to overwrite"; return false; } QString customDataFolder = getCustomDataFolder(); if (customDataFolder.length() > 0) { qDebug() << "Keeping basket data in" << customDataFolder; return false; // Do not delete the data folder. Note: old basketrc will not be deleted either } m_dataMigrator.reset(new Kdelibs4Migration()); if (m_dataMigrator->kdeHomeFound()) { bool copySucceeded = false; QEventLoop waitLoop; auto onCopyFinished = [&](KJob *job) { int error = job->error(); qDebug() << "KIO::CopyJob finished with result" << error; if (error != 0) { qDebug() << job->errorString(); QMessageBox::critical(NULL, QGuiApplication::applicationDisplayName(), i18n("Failed to migrate Basket data from KDE4. You will need to close Basket and copy the basket folder manually.\n" "Source: %1\nDestination: %2\nReason: %3", getOldDataDir(), getNewDataDir(), job->errorString())); } copySucceeded = (error == 0); waitLoop.quit(); }; qDebug() << "Kdelibs4Migration: start copying basket data"; KIO::CopyJob *copyJob = KIO::copyAs(QUrl::fromLocalFile(getOldDataDir()), QUrl::fromLocalFile(getNewDataDir())); QObject::connect(copyJob, &KIO::CopyJob::result, onCopyFinished); waitLoop.exec(); if (copySucceeded) return true; } return false; } void showPostMigrationDialog() { QMessageBox msgBox; msgBox.setWindowTitle(i18n("Choose action")); msgBox.setText( i18n("Basket data from KDE4 have been successfully migrated to %1.\n" "Unless you are planning to run KDE4 version again, you can delete the old folder %2", getNewDataDir(), getOldDataDir())); msgBox.addButton(i18n("Delete (to Trash)"), QMessageBox::YesRole); msgBox.addButton(i18n("Keep"), QMessageBox::NoRole); msgBox.setIcon(QMessageBox::Information); int dialogCode = msgBox.exec(); if (dialogCode == 0) { // ~/.kde/share/config/basketrc QString basketrc = m_dataMigrator->locateLocal("config", BASKET_RC); QList trashList = {QUrl::fromLocalFile(getOldDataDir())}; if (QFile(basketrc).exists()) trashList.prepend(QUrl::fromLocalFile(basketrc)); qDebug() << "Move to trash:" << trashList; KIO::trash(trashList); } } private: const QString BASKET_RC = "basketrc"; QScopedPointer m_dataMigrator; QString getOldConfig() { return m_dataMigrator->locateLocal("config", BASKET_RC); } QString getOldDataDir() { return m_dataMigrator->saveLocation("data", "basket"); } QString getNewDataDir() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/basket"; } QString getCustomDataFolder() { KSharedConfig::Ptr basketConfig = KSharedConfig::openConfig(BASKET_RC); KConfigGroup config = basketConfig->group("Main window"); return config.readEntry("dataFolder", ""); } }; #endif // KDE4_MIGRATION_H diff --git a/src/kgpgme.cpp b/src/kgpgme.cpp index 01fd79d..5446434 100644 --- a/src/kgpgme.cpp +++ b/src/kgpgme.cpp @@ -1,463 +1,449 @@ -/*************************************************************************** - * Copyright (C) 2006 by Petri Damsten * - * damu@iki.fi * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2006 Petri Damsten + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "config.h" #ifdef HAVE_LIBGPGME #include "debugwindow.h" #include "global.h" #include "kgpgme.h" #include #include #include #include #include #include #include #include #include #include #include #include #include //For errno #include //For LC_ALL, etc. #include //For write // KGpgSelKey class based on class in KGpg with the same name class KGpgSelKey : public QDialog { private: QTreeWidget *keysListpr; public: KGpgSelKey(QWidget *parent, const char *name, QString preselected, const KGpgMe &gpg) : QDialog(parent) { // Dialog options setObjectName(name); setModal(true); setWindowTitle(i18n("Private Key List")); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); QString keyname; QVBoxLayout *vbox; QWidget *page = new QWidget(this); QLabel *labeltxt; QPixmap keyPair = QIcon::fromTheme("kgpg_key2").pixmap(20, 20); setMinimumSize(350, 100); keysListpr = new QTreeWidget(page); keysListpr->setRootIsDecorated(true); keysListpr->setColumnCount(3); QStringList headers; headers << i18n("Name") << i18n("Email") << i18n("ID"); keysListpr->setHeaderLabels(headers); keysListpr->setAllColumnsShowFocus(true); labeltxt = new QLabel(i18n("Choose a secret key:"), page); vbox = new QVBoxLayout(page); KGpgKeyList list = gpg.keys(true); for (KGpgKeyList::iterator it = list.begin(); it != list.end(); ++it) { QString name = gpg.checkForUtf8((*it).name); QStringList values; values << name << (*it).email << (*it).id; QTreeWidgetItem *item = new QTreeWidgetItem(keysListpr, values, 3); item->setIcon(0, keyPair); if (preselected == (*it).id) { item->setSelected(true); keysListpr->setCurrentItem(item); } } if (!keysListpr->currentItem() && keysListpr->topLevelItemCount() > 0) { keysListpr->topLevelItem(0)->setSelected(true); keysListpr->setCurrentItem(keysListpr->topLevelItem(0)); } vbox->addWidget(labeltxt); vbox->addWidget(keysListpr); mainLayout->addWidget(page); 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, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); }; QString key() { QTreeWidgetItem *item = keysListpr->currentItem(); if (item) return item->text(2); return ""; } }; KGpgMe::KGpgMe() : m_ctx(0) , m_useGnuPGAgent(true) { init(GPGME_PROTOCOL_OpenPGP); if (gpgme_new(&m_ctx) == GPG_ERR_NO_ERROR) { gpgme_set_armor(m_ctx, 1); setPassphraseCb(); // Set gpg version gpgme_engine_info_t info; gpgme_get_engine_info(&info); while (info != NULL && info->protocol != gpgme_get_protocol(m_ctx)) { info = info->next; } if (info != NULL) { QByteArray gpgPath = info->file_name; gpgPath.replace("gpg2", "gpg"); // require GnuPG v1 gpgme_ctx_set_engine_info(m_ctx, GPGME_PROTOCOL_OpenPGP, gpgPath.data(), NULL); } } else { m_ctx = 0; } } KGpgMe::~KGpgMe() { if (m_ctx) gpgme_release(m_ctx); clearCache(); } void KGpgMe::clearCache() { if (m_cache.size() > 0) { m_cache.fill('\0'); m_cache.truncate(0); } } void KGpgMe::init(gpgme_protocol_t proto) { gpgme_error_t err; gpgme_check_version("1.0.0"); // require GnuPG v1 setlocale(LC_ALL, ""); gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL)); #ifndef Q_OS_WIN gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL)); #endif err = gpgme_engine_check_version(proto); if (err) { static QString lastErrorText; QString text = QString("%1: %2").arg(gpgme_strsource(err), gpgme_strerror(err)); if (text != lastErrorText) { KMessageBox::error(qApp->activeWindow(), text); lastErrorText = text; } } } QString KGpgMe::checkForUtf8(QString txt) { // code borrowed from KGpg which borrowed it from gpa const char *s; // Make sure the encoding is UTF-8. // Test structure suggested by Werner Koch if (txt.isEmpty()) return QString(); for (s = txt.toLatin1(); *s && !(*s & 0x80); s++) ; if (*s && !strchr(txt.toLatin1(), 0xc3) && (txt.indexOf("\\x") == -1)) return txt; // The string is not in UTF-8 // if (strchr (txt.toLatin1(), 0xc3)) return (txt+" +++"); if (txt.indexOf("\\x") == -1) return QString::fromUtf8(txt.toLatin1()); // if (!strchr (txt.toLatin1(), 0xc3) || (txt.indexOf("\\x")!=-1)) { for (int idx = 0; (idx = txt.indexOf("\\x", idx)) >= 0; ++idx) { char str[2] = "x"; str[0] = (char)QString(txt.mid(idx + 2, 2)).toShort(0, 16); txt.replace(idx, 4, str); } if (!strchr(txt.toLatin1(), 0xc3)) return QString::fromUtf8(txt.toLatin1()); else return QString::fromUtf8(QString::fromUtf8(txt.toLatin1()).toLatin1()); // perform Utf8 twice, or some keys display badly return txt; } QString KGpgMe::selectKey(QString previous) { QPointer dlg = new KGpgSelKey(qApp->activeWindow(), "", previous, *this); if (dlg->exec()) return dlg->key(); return ""; } // Rest of the code is mainly based in gpgme examples KGpgKeyList KGpgMe::keys(bool privateKeys /* = false */) const { KGpgKeyList keys; gpgme_error_t err = 0, err2 = 0; gpgme_key_t key = 0; gpgme_keylist_result_t result = 0; if (m_ctx) { err = gpgme_op_keylist_start(m_ctx, NULL, privateKeys); if (!err) { while (!(err = gpgme_op_keylist_next(m_ctx, &key))) { KGpgKey gpgkey; if (!key->subkeys) continue; gpgkey.id = key->subkeys->keyid; if (key->uids) { gpgkey.name = key->uids->name; gpgkey.email = key->uids->email; } keys.append(gpgkey); gpgme_key_unref(key); } if (gpg_err_code(err) == GPG_ERR_EOF) err = 0; err2 = gpgme_op_keylist_end(m_ctx); if (!err) err = err2; } } if (err) { KMessageBox::error(qApp->activeWindow(), QString("%1: %2").arg(gpgme_strsource(err)).arg(gpgme_strerror(err))); } else { result = gpgme_op_keylist_result(m_ctx); if (result->truncated) { KMessageBox::error(qApp->activeWindow(), i18n("Key listing unexpectedly truncated.")); } } return keys; } bool KGpgMe::encrypt(const QByteArray &inBuffer, unsigned long length, QByteArray *outBuffer, QString keyid /* = QString() */) { gpgme_error_t err = 0; gpgme_data_t in = 0, out = 0; gpgme_key_t keys[2] = {NULL, NULL}; gpgme_key_t *key = NULL; gpgme_encrypt_result_t result = 0; outBuffer->resize(0); if (m_ctx) { err = gpgme_data_new_from_mem(&in, inBuffer.data(), length, 1); if (!err) { err = gpgme_data_new(&out); if (!err) { if (keyid.isNull()) { key = NULL; } else { err = gpgme_get_key(m_ctx, keyid.toLatin1(), &keys[0], 0); key = keys; } if (!err) { err = gpgme_op_encrypt(m_ctx, key, GPGME_ENCRYPT_ALWAYS_TRUST, in, out); if (!err) { result = gpgme_op_encrypt_result(m_ctx); if (result->invalid_recipients) { KMessageBox::error(qApp->activeWindow(), QString("%1: %2").arg(i18n("That public key is not meant for encryption")).arg(result->invalid_recipients->fpr)); } else { err = readToBuffer(out, outBuffer); } } } } } } if (err != GPG_ERR_NO_ERROR && err != GPG_ERR_CANCELED) { KMessageBox::error(qApp->activeWindow(), QString("%1: %2").arg(gpgme_strsource(err)).arg(gpgme_strerror(err))); } if (err != GPG_ERR_NO_ERROR) { DEBUG_WIN << "KGpgMe::encrypt error: " + QString::number(err); clearCache(); } if (keys[0]) gpgme_key_unref(keys[0]); if (in) gpgme_data_release(in); if (out) gpgme_data_release(out); return (err == GPG_ERR_NO_ERROR); } bool KGpgMe::decrypt(const QByteArray &inBuffer, QByteArray *outBuffer) { gpgme_error_t err = 0; gpgme_data_t in = 0, out = 0; gpgme_decrypt_result_t result = 0; outBuffer->resize(0); if (m_ctx) { err = gpgme_data_new_from_mem(&in, inBuffer.data(), inBuffer.size(), 1); if (!err) { err = gpgme_data_new(&out); if (!err) { err = gpgme_op_decrypt(m_ctx, in, out); if (!err) { result = gpgme_op_decrypt_result(m_ctx); if (result->unsupported_algorithm) { KMessageBox::error(qApp->activeWindow(), QString("%1: %2").arg(i18n("Unsupported algorithm")).arg(result->unsupported_algorithm)); } else { err = readToBuffer(out, outBuffer); } } } } } if (err != GPG_ERR_NO_ERROR && err != GPG_ERR_CANCELED) { KMessageBox::error(qApp->activeWindow(), QString("%1: %2").arg(gpgme_strsource(err)).arg(gpgme_strerror(err))); } if (err != GPG_ERR_NO_ERROR) clearCache(); if (in) gpgme_data_release(in); if (out) gpgme_data_release(out); return (err == GPG_ERR_NO_ERROR); } #define BUF_SIZE (32 * 1024) gpgme_error_t KGpgMe::readToBuffer(gpgme_data_t in, QByteArray *outBuffer) const { int ret; gpgme_error_t err = GPG_ERR_NO_ERROR; ret = gpgme_data_seek(in, 0, SEEK_SET); if (ret) { err = gpgme_err_code_from_errno(errno); } else { char *buf = new char[BUF_SIZE + 2]; if (buf) { while ((ret = gpgme_data_read(in, buf, BUF_SIZE)) > 0) { uint size = outBuffer->size(); outBuffer->resize(size + ret); memcpy(outBuffer->data() + size, buf, ret); } if (ret < 0) err = gpgme_err_code_from_errno(errno); delete[] buf; } } return err; } bool KGpgMe::isGnuPGAgentAvailable() { QString agent_info = qgetenv("GPG_AGENT_INFO"); if (agent_info.indexOf(':') > 0) return true; return false; } void KGpgMe::setPassphraseCb() { bool agent = false; QString agent_info; agent_info = qgetenv("GPG_AGENT_INFO"); if (m_useGnuPGAgent) { if (agent_info.indexOf(':')) agent = true; if (agent_info.startsWith(QLatin1String("disable:"))) setenv("GPG_AGENT_INFO", agent_info.mid(8).toLatin1(), 1); } else { if (!agent_info.startsWith(QLatin1String("disable:"))) setenv("GPG_AGENT_INFO", "disable:" + agent_info.toLatin1(), 1); } if (agent) gpgme_set_passphrase_cb(m_ctx, 0, 0); else gpgme_set_passphrase_cb(m_ctx, passphraseCb, this); } gpgme_error_t KGpgMe::passphraseCb(void *hook, const char *uid_hint, const char *passphrase_info, int last_was_bad, int fd) { KGpgMe *gpg = static_cast(hook); return gpg->passphrase(uid_hint, passphrase_info, last_was_bad, fd); } gpgme_error_t KGpgMe::passphrase(const char *uid_hint, const char * /*passphrase_info*/, int last_was_bad, int fd) { QString s; QString gpg_hint = checkForUtf8(uid_hint); bool canceled = false; if (last_was_bad) { s += "" + i18n("Wrong password.") + "

    \n\n"; clearCache(); } if (!m_text.isEmpty()) s += m_text + "
    "; if (!gpg_hint.isEmpty()) s += gpg_hint; if (m_cache.isEmpty()) { KPasswordDialog dlg; dlg.setPrompt(s); if (m_saving) dlg.setWindowTitle(i18n("Please enter a new password:")); if (dlg.exec()) m_cache = dlg.password(); else canceled = true; } if (!canceled) write(fd, m_cache.data(), m_cache.length()); write(fd, "\n", 1); return canceled ? GPG_ERR_CANCELED : GPG_ERR_NO_ERROR; } #endif // HAVE_LIBGPGME diff --git a/src/kgpgme.h b/src/kgpgme.h index ace2b2f..b203c3a 100644 --- a/src/kgpgme.h +++ b/src/kgpgme.h @@ -1,94 +1,80 @@ -/*************************************************************************** - * Copyright (C) 2006 by Petri Damsten * - * damu@iki.fi * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2006 Petri Damsten + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef KGPGME_H #define KGPGME_H #include #ifdef HAVE_LIBGPGME #include #include #include /** @author Petri Damsten */ class KGpgKey { public: QString id; QString name; QString email; }; typedef QList KGpgKeyList; class KGpgMe { public: KGpgMe(); ~KGpgMe(); QString selectKey(QString previous = QString()); KGpgKeyList keys(bool privateKeys = false) const; void setText(QString text, bool saving) { m_text = text; m_saving = saving; }; void setUseGnuPGAgent(bool use) { m_useGnuPGAgent = use; setPassphraseCb(); }; QString text() const { return m_text; }; bool saving() const { return m_saving; }; void clearCache(); bool encrypt(const QByteArray &inBuffer, unsigned long length, QByteArray *outBuffer, QString keyid = QString()); bool decrypt(const QByteArray &inBuffer, QByteArray *outBuffer); static QString checkForUtf8(QString txt); static bool isGnuPGAgentAvailable(); private: gpgme_ctx_t m_ctx; QString m_text; bool m_saving; bool m_useGnuPGAgent; QString m_cache; void init(gpgme_protocol_t proto); gpgme_error_t readToBuffer(gpgme_data_t in, QByteArray *outBuffer) const; void setPassphraseCb(); static gpgme_error_t passphraseCb(void *hook, const char *uid_hint, const char *passphrase_info, int last_was_bad, int fd); gpgme_error_t passphrase(const char *uid_hint, const char *passphrase_info, int last_was_bad, int fd); }; #endif // HAVE_LIBGPGME #endif // KGPGME_H diff --git a/src/ksystemtrayicon2.cpp b/src/ksystemtrayicon2.cpp deleted file mode 100644 index adc3b86..0000000 --- a/src/ksystemtrayicon2.cpp +++ /dev/null @@ -1,178 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2008 by Kelvie Wong * - * kelvie@ieee.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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#include "ksystemtrayicon2.h" - -// Added by qt3to4: -#include -#include -#include - -#include - -#include // To know the program name: -#include -#include - -#include -#include -#include -#include - -KSystemTray2::KSystemTray2(QWidget *parent, const char *name) - : KSystemTrayIcon(parent) - , QWidget(parent) -{ -} - -KSystemTray2::~KSystemTray2() -{ -} - -void KSystemTray2::displayCloseMessage(QString fileMenu) -{ - /* IDEAS OF IMPROVEMENTS: - * - Use queuedMessageBox() but it need a dontAskAgainName parameter - * and image in QMimeSourceFactory shouldn't be removed. - * - Sometimes the systray icon is covered (a passive popup...) - * Use XComposite extension, if available, to get the kicker pixmap. - * - Perhaps desaturate the area around the proper SysTray icon, - * helping bring it into sharper focus. Or draw the circle with XOR - * brush. - * - Perhaps add the icon in the text (eg. "... in the - * system tray ([icon])."). Add some clutter to the dialog. - */ -#if KDE_IS_VERSION(3, 1, 90) - // Don't do all the computations if they are unneeded: - if (!KMessageBox::shouldBeShownContinue("hideOnCloseInfo")) - return; -#endif - // "Default parameter". Here, to avoid a i18n() call and dependency in the .h - if (fileMenu.isEmpty()) - fileMenu = i18n("File"); - - // Some values we need: - QPoint g = mapToGlobal(pos()); - int desktopWidth = qApp->desktop()->width(); - int desktopHeight = qApp->desktop()->height(); - int tw = width(); - int th = height(); - - // We are trying to make a live screenshot of the systray icon to circle it - // and show it to the user. If no systray is used or if the icon is not visible, - // we should not show that screenshot but only a text! - - // 1. Determine if the user use a system tray area or not: - /*QByteArray screenstr; - screenstr.setNum(qt_xscreen()); - QByteArrray trayatom = "_NET_SYSTEM_TRAY_S" + screenstr; - bool useSystray = (KSelectionWatcher(trayatom).owner() != 0L);*/ - bool useSystray = true; - - // 2. And then if the icon is visible too (eg. this->show() has been called): - useSystray = useSystray && KSystemTrayIcon::isVisible(); - - // 3. Kicker (or another systray manager) can be visible but masked out of - // the screen (ie. on right or on left of it). We check if the icon isn't - // out of screen. - if (useSystray) { - QRect deskRect(0, 0, desktopWidth, desktopHeight); - if (!deskRect.contains(g.x(), g.y()) || !deskRect.contains(g.x() + tw, g.y() + th)) - useSystray = false; - } - - // 4. We raise the window containing the systray icon (typically the kicker) to - // have the most chances it is visible during the capture: - /* if (useSystray) { - // We are testing if one of the corners is hidden, and if yes, we would enter - // a time consuming process (raise kicker and wait some time): - // if (qApp->widgetAt(g) != this || - // qApp->widgetAt(g + QPoint(tw-1, 0)) != this || - // qApp->widgetAt(g + QPoint(0, th-1)) != this || - // qApp->widgetAt(g + QPoint(tw-1, th-1)) != this) { - int systrayManagerWinId = topLevelWidget()->winId(); - KWindowSystem::forceActiveWindow(systrayManagerWinId); - qApp->processEvents(); // Because without it the systrayManager is raised only after the messageBox is displayed - // KWindowSystem::activateWindow(systrayManagerWinId); - // qApp->processEvents(); // Because without it the systrayManager is raised only after the messageBox is displayed - // KWindowSystem::raiseWindow(systrayManagerWinId); - // qApp->processEvents(); // Because without it the systrayManager is raised only after the messageBox is displayed - sleep(1); - // TODO: Re-verify that at least one corner is now visible - // } - }*/ - - // KMessageBox::information(this, QString::number(g.x()) + ":" + QString::number(g.y()) + ":" + - // QString::number((int)(qApp->widgetAt(g+QPoint(1,1))))); - - QString message = i18n( - "

    Closing the main window will keep %1 running in the system tray. " - "Use Quit from the Basket menu to quit the application.

    ", - QGuiApplication::applicationDisplayName()); - // We are sure the systray icon is visible: ouf! - if (useSystray) { - // Compute size and position of the pixmap to be grabbed: - int w = desktopWidth / 4; - int h = desktopHeight / 9; - int x = g.x() + tw / 2 - w / 2; // Center the rectangle in the systray icon - int y = g.y() + th / 2 - h / 2; - if (x < 0) - x = 0; // Move the rectangle to stay in the desktop limits - if (y < 0) - y = 0; - if (x + w > desktopWidth) - x = desktopWidth - w; - if (y + h > desktopHeight) - y = desktopHeight - h; - - // Grab the desktop and draw a circle around the icon: - QPixmap shot = QPixmap::grabWindow(QX11Info::appRootWindow(), x, y, w, h); - QPainter painter(&shot); - const int CIRCLE_MARGINS = 6; - const int CIRCLE_WIDTH = 3; - const int SHADOW_OFFSET = 1; - const int IMAGE_BORDER = 1; - int ax = g.x() - x - CIRCLE_MARGINS - 1; - int ay = g.y() - y - CIRCLE_MARGINS - 1; - painter.setPen(QPen(QApplication::palette().dark(), CIRCLE_WIDTH)); - painter.drawArc(ax + SHADOW_OFFSET, ay + SHADOW_OFFSET, tw + 2 * CIRCLE_MARGINS, th + 2 * CIRCLE_MARGINS, 0, 16 * 360); - painter.setPen(QPen(Qt::red /*QApplication::palette().active().highlight()*/, CIRCLE_WIDTH)); - painter.drawArc(ax, ay, tw + 2 * CIRCLE_MARGINS, th + 2 * CIRCLE_MARGINS, 0, 16 * 360); -#if 1 - // Draw the pixmap over the screenshot in case a window hide the icon: - painter.drawPixmap(g.x() - x, g.y() - y + 1, QSystemTrayIcon::icon().pixmap(0)); -#endif - painter.end(); - - // Then, we add a border around the image to make it more visible: - QPixmap finalShot(w + 2 * IMAGE_BORDER, h + 2 * IMAGE_BORDER); - finalShot.fill(QApplication::palette().foreground().color()); - painter.begin(&finalShot); - painter.drawPixmap(IMAGE_BORDER, IMAGE_BORDER, shot); - painter.end(); - - // Associate source to image and show the dialog: - QResource::registerResource(finalShot.toImage().bits(), "systray_shot"); - KMessageBox::information(qApp->activeWindow(), message + "

    ", i18n("Docking in System Tray"), "hideOnCloseInfo"); - QResource::unregisterResource(finalShot.toImage().bits(), "systray_shot"); - } else { - KMessageBox::information(qApp->activeWindow(), message, i18n("Docking in System Tray"), "hideOnCloseInfo"); - } -} diff --git a/src/ksystemtrayicon2.h b/src/ksystemtrayicon2.h deleted file mode 100644 index 0eeea6a..0000000 --- a/src/ksystemtrayicon2.h +++ /dev/null @@ -1,49 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2008 by Kelvie Wong * - * kelvie@ieee.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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#ifndef K_SYSTEM_TRAY_ICON -#define K_SYSTEM_TRAY_ICON - -#include -#include - -/** Convenient class to develop the displayCloseMessage() dialog - * hopefully integrated in KDE 3.4 - * @author Sébastien Laoût - */ -class KSystemTray2 : public KSystemTrayIcon, public QWidget -{ - Q_OBJECT -public: - explicit KSystemTray2(QWidget *parent = 0, const char *name = 0); - ~KSystemTray2(); - /** - * Call this method when the user clicked the close button of the window - * (the [x]) to inform him that the application sit in the system tray - * and willn't be closed (as he is used to). - * - * You usually call it from reimplemented KMainWindow::queryClose() - * - * @since 3.4 - */ - void displayCloseMessage(QString fileMenu = ""); -}; - -#endif // K_SYSTEM_TRAY_ICON diff --git a/src/likeback.cpp b/src/likeback.cpp index 5b07331..93daa64 100644 --- a/src/likeback.cpp +++ b/src/likeback.cpp @@ -1,811 +1,798 @@ -/*************************************************************************** - * Copyright (C) 2006 by Sébastien Laoût * - * * - * This program 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 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 Library 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. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2006 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "likeback.h" #include "aboutdata.h" #include "global.h" #include "likeback_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /****************************************/ /********** class LikeBackBar: **********/ /****************************************/ LikeBackBar::LikeBackBar(LikeBack *likeBack) : QWidget(0, Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint) , m_likeBack(likeBack) { QHBoxLayout *layout = new QHBoxLayout(this); QIcon likeIconSet = QIcon::fromTheme("likeback_like.png"); QIcon dislikeIconSet = QIcon::fromTheme("likeback_dislike.png"); QIcon bugIconSet = QIcon::fromTheme("likeback_bug.png"); QIcon featureIconSet = QIcon::fromTheme("likeback_feature.png"); m_likeButton = new QToolButton(this); m_likeButton->setIcon(likeIconSet); m_likeButton->setText("

    " + i18n("Send application developers a comment about something you like")); m_likeButton->setAutoRaise(true); connect(m_likeButton, SIGNAL(clicked()), this, SLOT(clickedLike())); layout->addWidget(m_likeButton); m_dislikeButton = new QToolButton(this); m_dislikeButton->setIcon(dislikeIconSet); m_dislikeButton->setText("

    " + i18n("Send application developers a comment about something you dislike")); m_dislikeButton->setAutoRaise(true); connect(m_dislikeButton, SIGNAL(clicked()), this, SLOT(clickedDislike())); layout->addWidget(m_dislikeButton); m_bugButton = new QToolButton(this); m_bugButton->setIcon(bugIconSet); m_bugButton->setText("

    " + i18n("Send application developers a comment about an improper behavior of the application")); m_bugButton->setAutoRaise(true); connect(m_bugButton, SIGNAL(clicked()), this, SLOT(clickedBug())); layout->addWidget(m_bugButton); m_featureButton = new QToolButton(this); m_featureButton->setIcon(featureIconSet); m_featureButton->setText("

    " + i18n("Send application developers a comment about a new feature you desire")); m_featureButton->setAutoRaise(true); connect(m_featureButton, SIGNAL(clicked()), this, SLOT(clickedFeature())); layout->addWidget(m_featureButton); connect(&m_timer, SIGNAL(timeout()), this, SLOT(autoMove())); LikeBack::Button buttons = likeBack->buttons(); m_likeButton->setVisible(buttons & LikeBack::Like); m_dislikeButton->setVisible(buttons & LikeBack::Dislike); m_bugButton->setVisible(buttons & LikeBack::Bug); m_featureButton->setVisible(buttons & LikeBack::Feature); } LikeBackBar::~LikeBackBar() { } void LikeBackBar::startTimer() { m_timer.start(10); } void LikeBackBar::stopTimer() { m_timer.stop(); } void LikeBackBar::autoMove() { static QWidget *lastWindow = 0; QWidget *window = qApp->activeWindow(); // When a Kicker applet has the focus, like the Commandline QLineEdit, // the systemtray icon indicates to be the current window and the LikeBack is shown next to the system tray icon. // It's obviously bad ;-) : bool shouldShow = (m_likeBack->userWantsToShowBar() && m_likeBack->enabledBar() && window && !window->inherits("KSystemTray")); if (shouldShow) { // move(window->x() + window->width() - 100 - width(), window->y()); // move(window->x() + window->width() - 100 - width(), window->mapToGlobal(QPoint(0, 0)).y() - height()); move(window->mapToGlobal(QPoint(0, 0)).x() + window->width() - width(), window->mapToGlobal(QPoint(0, 0)).y() + 1); if (window != lastWindow && m_likeBack->windowNamesListing() != LikeBack::NoListing) { if (window->objectName().isEmpty() || window->objectName() == "unnamed") { qDebug() << "===== LikeBack ===== UNNAMED ACTIVE WINDOW OF TYPE " << window->metaObject()->className() << " ======" << LikeBack::activeWindowPath(); } else if (m_likeBack->windowNamesListing() == LikeBack::AllWindows) { qDebug() << "LikeBack: Active Window: " << LikeBack::activeWindowPath(); } } lastWindow = window; } // Show or hide the bar accordingly: if (shouldShow && !isVisible()) { show(); } else if (!shouldShow && isVisible()) { hide(); } } void LikeBackBar::clickedLike() { m_likeBack->execCommentDialog(LikeBack::Like); } void LikeBackBar::clickedDislike() { m_likeBack->execCommentDialog(LikeBack::Dislike); } void LikeBackBar::clickedBug() { m_likeBack->execCommentDialog(LikeBack::Bug); } void LikeBackBar::clickedFeature() { m_likeBack->execCommentDialog(LikeBack::Feature); } /********************************************/ /********** class LikeBackPrivate: **********/ /********************************************/ LikeBackPrivate::LikeBackPrivate() : bar(0) , config(0) , aboutData(0) , buttons(LikeBack::DefaultButtons) , hostName() , remotePath() , hostPort(80) , acceptedLocales() , acceptedLanguagesMessage() , windowListing(LikeBack::NoListing) , showBar(false) , disabledCount(0) , fetchedEmail() , action(0) { } LikeBackPrivate::~LikeBackPrivate() { delete bar; delete action; config = 0; aboutData = 0; } /*************************************/ /********** class LikeBack: **********/ /*************************************/ LikeBack::LikeBack(Button buttons, bool showBarByDefault, KConfig *config, const KAboutData *aboutData) : QObject() { // Initialize properties (1/2): d = new LikeBackPrivate(); d->buttons = buttons; d->config = config; d->aboutData = aboutData; d->showBarByDefault = showBarByDefault; // Use default KApplication config and aboutData if not provided: if (d->config == 0) d->config = KSharedConfig::openConfig().data(); if (d->aboutData == 0) d->aboutData = new KAboutData(KAboutData::applicationData()); // Initialize properties (2/2) [Needs aboutData to be set]: d->showBar = userWantsToShowBar(); // Fetch the KControl user email address as a default one: if (!emailAddressAlreadyProvided()) fetchUserEmail(); // Initialize the button-bar: d->bar = new LikeBackBar(this); d->bar->resize(d->bar->sizeHint()); // Show the information message if it is the first time, and if the button-bar is shown: static const char *messageShown = "LikeBack_starting_information"; if (d->showBar && KMessageBox::shouldBeShownContinue(messageShown)) { showInformationMessage(); KMessageBox::saveDontShowAgainContinue(messageShown); } // Show the bar if that's wanted by the developer or the user: if (d->showBar) QTimer::singleShot(0, d->bar, SLOT(startTimer())); #if 0 disableBar(); // Alex: Oh, it drove me nuts d->buttons = (Button)(0); showInformationMessage(); d->buttons = (Button)(Feature); showInformationMessage(); d->buttons = (Button)(Bug); showInformationMessage(); d->buttons = (Button)(Bug | Feature); showInformationMessage(); d->buttons = (Button)(Dislike); showInformationMessage(); d->buttons = (Button)(Dislike | Feature); showInformationMessage(); d->buttons = (Button)(Dislike | Bug); showInformationMessage(); d->buttons = (Button)(Dislike | Bug | Feature); showInformationMessage(); d->buttons = (Button)(Like); showInformationMessage(); d->buttons = (Button)(Like | Feature); showInformationMessage(); d->buttons = (Button)(Like | Bug); showInformationMessage(); d->buttons = (Button)(Like | Bug | Feature); showInformationMessage(); d->buttons = (Button)(Like | Dislike); showInformationMessage(); d->buttons = (Button)(Like | Dislike | Feature); showInformationMessage(); d->buttons = (Button)(Like | Dislike | Bug); showInformationMessage(); d->buttons = (Button)(Like | Dislike | Bug | Feature); showInformationMessage(); enableBar(); #endif } LikeBack::~LikeBack() { delete d; } void LikeBack::setWindowNamesListing(WindowListing windowListing) { d->windowListing = windowListing; } LikeBack::WindowListing LikeBack::windowNamesListing() { return d->windowListing; } void LikeBack::setAcceptedLanguages(const QStringList &locales, const QString &message) { d->acceptedLocales = locales; d->acceptedLanguagesMessage = message; } QStringList LikeBack::acceptedLocales() { return d->acceptedLocales; } QString LikeBack::acceptedLanguagesMessage() { return d->acceptedLanguagesMessage; } void LikeBack::setServer(const QString &hostName, const QString &remotePath, quint16 hostPort) { d->hostName = hostName; d->remotePath = remotePath; d->hostPort = hostPort; } QString LikeBack::hostName() { return d->hostName; } QString LikeBack::remotePath() { return d->remotePath; } quint16 LikeBack::hostPort() { return d->hostPort; } void LikeBack::disableBar() { d->disabledCount++; if (d->bar && d->disabledCount > 0) { d->bar->hide(); d->bar->stopTimer(); } } void LikeBack::enableBar() { d->disabledCount--; if (d->disabledCount < 0) qDebug() << "===== LikeBack ===== Enabled more times than it was disabled. Please refer to the disableBar() documentation for more information and hints."; if (d->bar && d->disabledCount <= 0) { d->bar->startTimer(); } } bool LikeBack::enabledBar() { return d->disabledCount <= 0; } void LikeBack::execCommentDialog(Button type, const QString &initialComment, const QString &windowPath, const QString &context) { disableBar(); QPointer dialog = new LikeBackDialog(type, initialComment, windowPath, context, this); dialog->exec(); enableBar(); } void LikeBack::execCommentDialogFromHelp() { execCommentDialog(AllButtons, /*initialComment=*/"", /*windowPath=*/"HelpMenuAction"); } LikeBack::Button LikeBack::buttons() { return d->buttons; } const KAboutData *LikeBack::aboutData() { return d->aboutData; } KConfig *LikeBack::config() { return d->config; } QAction *LikeBack::sendACommentAction(KActionCollection *parent) { if (d->action == 0) { d->action = parent->addAction("likeback_send_a_comment", this, SLOT(execCommentDialog())); d->action->setText(i18n("&Send a Comment to Developers")); d->action->setIcon(QIcon::fromTheme("mail-message-new")); } return d->action; } bool LikeBack::userWantsToShowBar() { // Store the button-bar per version, so it can be disabled by the developer for the final version: KConfigGroup configGroup = KSharedConfig::openConfig()->group("LikeBack"); return configGroup.readEntry("userWantToShowBarForVersion_" + d->aboutData->version(), d->showBarByDefault); } void LikeBack::setUserWantsToShowBar(bool showBar) { if (showBar == d->showBar) return; d->showBar = showBar; // Store the button-bar per version, so it can be disabled by the developer for the final version: KConfigGroup configGroup = KSharedConfig::openConfig()->group("LikeBack"); configGroup.writeEntry("userWantToShowBarForVersion_" + d->aboutData->version(), showBar); configGroup.sync(); // Make sure the option is saved, even if the application crashes after that. if (showBar) d->bar->startTimer(); } void LikeBack::showInformationMessage() { // Show a message reflecting the allowed types of comment: Button buttons = d->buttons; int nbButtons = (buttons & Like ? 1 : 0) + (buttons & Dislike ? 1 : 0) + (buttons & Bug ? 1 : 0) + (buttons & Feature ? 1 : 0); KMessageBox::information( 0, "

    " + (isDevelopmentVersion(d->aboutData->version()) ? i18n("Welcome to this testing version of %1.", QGuiApplication::applicationDisplayName()) : i18n("Welcome to %1.", QGuiApplication::applicationDisplayName())) + "

    " "

    " + i18n("To help us improve it, your comments are important.") + "

    " "

    " + ((buttons & LikeBack::Like) && (buttons & LikeBack::Dislike) ? i18n("Each time you have a great or frustrating experience, " "please click the appropriate face below the window title-bar, " "briefly describe what you like or dislike and click Send.") : (buttons & LikeBack::Like ? i18n("Each time you have a great experience, " "please click the smiling face below the window title-bar, " "briefly describe what you like and click Send.") : (buttons & LikeBack::Dislike ? i18n("Each time you have a frustrating experience, " "please click the frowning face below the window title-bar, " "briefly describe what you dislike and click Send.") : QString()))) + "

    " + (buttons & LikeBack::Bug ? "

    " + (buttons & (LikeBack::Like | LikeBack::Dislike) ? i18n("Follow the same principle to quickly report a bug: " "just click the broken-object icon in the top-right corner of the window, describe it and click Send.") : i18n("Each time you discover a bug in the application, " "please click the broken-object icon below the window title-bar, " "briefly describe the mis-behaviour and click Send.")) + "

    " : "") + "

    " + i18np("Example:", "Examples:", nbButtons) + "

    " + (buttons & LikeBack::Like ? "

     " + i18n("I like the new artwork. Very refreshing.") + "

    " : "") + (buttons & LikeBack::Dislike ? "

     " + i18n("I dislike the welcome page of that assistant. Too time consuming.") + "

    " : "") + (buttons & LikeBack::Bug ? "

     " + i18n("The application has an improper behaviour when clicking the Add button. Nothing happens.") + "

    " : "") + (buttons & LikeBack::Feature ? "

     " + i18n("I desire a new feature allowing me to send my work by email.") + "

    " : "") + "", i18n("Help Improve the Application")); } QString LikeBack::activeWindowPath() { // Compute the window hierarchy (from the latest to the oldest): QStringList windowNames; QWidget *window = qApp->activeWindow(); while (window) { QString name = window->objectName(); // Append the class name to the window name if it is unnamed: if (name == "unnamed") name += QString(":") + window->metaObject()->className(); windowNames.append(name); window = dynamic_cast(window->parent()); } // Create the string of windows starting by the end (from the oldest to the latest): QString windowPath; for (int i = ((int)windowNames.count()) - 1; i >= 0; i--) { if (windowPath.isEmpty()) windowPath = windowNames[i]; else windowPath += QString("~~") + windowNames[i]; } // Finally return the computed path: return windowPath; } bool LikeBack::emailAddressAlreadyProvided() { KConfigGroup configGroup = KSharedConfig::openConfig()->group("LikeBack"); return configGroup.readEntry("emailAlreadyAsked", false); } QString LikeBack::emailAddress() { if (!emailAddressAlreadyProvided()) askEmailAddress(); KConfigGroup configGroup = KSharedConfig::openConfig()->group("LikeBack"); return configGroup.readEntry("emailAddress", ""); } void LikeBack::setEmailAddress(const QString &address, bool userProvided) { KConfigGroup configGroup = KSharedConfig::openConfig()->group("LikeBack"); configGroup.writeEntry("emailAddress", address); configGroup.writeEntry("emailAlreadyAsked", userProvided || emailAddressAlreadyProvided()); configGroup.sync(); // Make sure the option is saved, even if the application crashes after that. } void LikeBack::askEmailAddress() { KConfigGroup configGroup = KSharedConfig::openConfig()->group("LikeBack"); QString currentEmailAddress = configGroup.readEntry("emailAddress", ""); if (!emailAddressAlreadyProvided() && !d->fetchedEmail.isEmpty()) currentEmailAddress = d->fetchedEmail; bool ok; /*QString emailExpString = "[\\w-\\.]+@[\\w-\\.]+\\.[\\w]+"; //QString namedEmailExpString = "[.]*[ \\t]+<" + emailExpString + '>'; //QRegExp emailExp("^(|" + emailExpString + '|' + namedEmailExpString + ")$"); QRegExp emailExp("^(|" + emailExpString + ")$"); QRegExpValidator emailValidator(emailExp, this);*/ disableBar(); QString email = QInputDialog::getText(qApp->activeWindow(), i18n("Email Address"), "

    " + i18n("Please provide your email address.") + "

    " + "

    " + i18n("It will only be used to contact you back if more information is needed about your comments, ask you how to reproduce the bugs you report, send bug corrections for you to test, etc.") + "

    " + "

    " + i18n("The email address is optional. If you do not provide any, your comments will be sent anonymously.") + "

    ", QLineEdit::Normal, currentEmailAddress, &ok /*, &emailValidator*/); enableBar(); if (ok) setEmailAddress(email); } // FIXME: Should be moved to KAboutData? Cigogne will also need it. bool LikeBack::isDevelopmentVersion(const QString &version) { return version.contains(QRegExp(".*(alpha|beta|rc|svn|cvs).*", Qt::CaseInsensitive)); } /** * Code from KBugReport::slotSetFrom() in kdeui/kbugreport.cpp: */ void LikeBack::fetchUserEmail() { // delete m_process; // m_process = 0; // m_configureEmail->setEnabled(true); // ### KDE4: why oh why is KEmailSettings in kio? KConfig emailConf(QString::fromLatin1("emaildefaults")); // find out the default profile KConfigGroup configGroup = KConfigGroup(&emailConf, QString::fromLatin1("Defaults")); QString profile = QString::fromLatin1("PROFILE_"); profile += configGroup.readEntry(QString::fromLatin1("Profile"), QString::fromLatin1("Default")); configGroup = KConfigGroup(&emailConf, profile); QString fromaddr = configGroup.readEntry(QString::fromLatin1("EmailAddress")); if (fromaddr.isEmpty()) { KUser userInfo; d->fetchedEmail = userInfo.property(KUser::FullName).toString(); } else { QString name = configGroup.readEntry(QString::fromLatin1("FullName")); if (!name.isEmpty()) d->fetchedEmail = /*name + QString::fromLatin1(" <") +*/ fromaddr /*+ QString::fromLatin1(">")*/; } // m_from->setText( fromaddr ); } /*******************************************/ /********** class LikeBackDialog: **********/ /*******************************************/ LikeBackDialog::LikeBackDialog(LikeBack::Button reason, const QString &initialComment, const QString &windowPath, const QString &context, LikeBack *likeBack) : QDialog(qApp->activeWindow()) , m_likeBack(likeBack) , m_windowPath(windowPath) , m_context(context) { // QDialog Options setWindowTitle(i18n("Send a Comment to Developers")); buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); okButton->setDefault(true); setParent(qApp->activeWindow()); setObjectName("_likeback_feedback_window_"); setModal(true); connect(okButton, SIGNAL(clicked()), SLOT(slotOk())); connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), SIGNAL(clicked()), SLOT(slotDefault())); // If no specific "reason" is provided, choose the first one: if (reason == LikeBack::AllButtons) { LikeBack::Button buttons = m_likeBack->buttons(); int firstButton = 0; if (firstButton == 0 && (buttons & LikeBack::Like)) firstButton = LikeBack::Like; if (firstButton == 0 && (buttons & LikeBack::Dislike)) firstButton = LikeBack::Dislike; if (firstButton == 0 && (buttons & LikeBack::Bug)) firstButton = LikeBack::Bug; if (firstButton == 0 && (buttons & LikeBack::Feature)) firstButton = LikeBack::Feature; reason = (LikeBack::Button)firstButton; } // If no window path is provided, get the current active window path: if (m_windowPath.isEmpty()) m_windowPath = LikeBack::activeWindowPath(); QWidget *page = new QWidget(this); QVBoxLayout *pageLayout = new QVBoxLayout(page); // The introduction message: QLabel *introduction = new QLabel(introductionText(), page); introduction->setWordWrap(true); pageLayout->addWidget(introduction); // The comment group: QGroupBox *box = new QGroupBox(i18n("Send Application Developers a Comment About:"), page); QVBoxLayout *boxLayout = new QVBoxLayout; box->setLayout(boxLayout); pageLayout->addWidget(box); // The radio buttons: QWidget *buttons = new QWidget(box); boxLayout->addWidget(buttons); // QGridLayout *buttonsGrid = new QGridLayout(buttons, /*nbRows=*/4, /*nbColumns=*/2, /*margin=*/0, spacingHint()); QGridLayout *buttonsGrid = new QGridLayout(buttons); if (m_likeBack->buttons() & LikeBack::Like) { QPixmap likePixmap = KIconLoader::global()->loadIcon(":images/16-actions-likeback_like.png", KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true); QLabel *likeIcon = new QLabel(buttons); likeIcon->setPixmap(likePixmap); likeIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); likeButton = new QRadioButton(i18n("Something you &like"), buttons); buttonsGrid->addWidget(likeIcon, /*row=*/0, /*column=*/0); buttonsGrid->addWidget(likeButton, /*row=*/0, /*column=*/1); } if (m_likeBack->buttons() & LikeBack::Dislike) { QPixmap dislikePixmap = KIconLoader::global()->loadIcon(":images/16-actions-likeback_dislike.png", KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true); QLabel *dislikeIcon = new QLabel(buttons); dislikeIcon->setPixmap(dislikePixmap); dislikeIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); dislikeButton = new QRadioButton(i18n("Something you &dislike"), buttons); buttonsGrid->addWidget(dislikeIcon, /*row=*/1, /*column=*/0); buttonsGrid->addWidget(dislikeButton, /*row=*/1, /*column=*/1); } if (m_likeBack->buttons() & LikeBack::Bug) { QPixmap bugPixmap = KIconLoader::global()->loadIcon(":images/16-actions-likeback_bug.png", KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true); QLabel *bugIcon = new QLabel(buttons); bugIcon->setPixmap(bugPixmap); bugIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); bugButton = new QRadioButton(i18n("An improper &behavior of this application"), buttons); buttonsGrid->addWidget(bugIcon, /*row=*/2, /*column=*/0); buttonsGrid->addWidget(bugButton, /*row=*/2, /*column=*/1); } if (m_likeBack->buttons() & LikeBack::Feature) { QPixmap featurePixmap = KIconLoader::global()->loadIcon(":images/16-actions-likeback_feature.png", KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true); QLabel *featureIcon = new QLabel(buttons); featureIcon->setPixmap(featurePixmap); featureIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); featureButton = new QRadioButton(i18n("A new &feature you desire"), buttons); buttonsGrid->addWidget(featureIcon, /*row=*/3, /*column=*/0); buttonsGrid->addWidget(featureButton, /*row=*/3, /*column=*/1); } // The comment text box: m_comment = new KTextEdit(box); boxLayout->addWidget(m_comment); m_comment->setTabChangesFocus(true); m_comment->setPlainText(initialComment); m_showButtons = new QCheckBox(i18n("Show comment buttons below &window titlebars"), page); m_showButtons->setChecked(m_likeBack->userWantsToShowBar()); pageLayout->addWidget(m_showButtons); connect(m_showButtons, SIGNAL(stateChanged(int)), this, SLOT(changeButtonBarVisible())); KGuiItem::assign(okButton, KGuiItem(i18n("&Send Comment"))); okButton->setEnabled(false); connect(m_comment, SIGNAL(textChanged()), this, SLOT(commentChanged())); KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults), KGuiItem(i18n("&Email Address..."))); resize(QSize(qApp->desktop()->width() * 1 / 2, qApp->desktop()->height() * 3 / 5).expandedTo(sizeHint())); QAction *sendShortcut = new QAction(this); sendShortcut->setShortcut(Qt::CTRL + Qt::Key_Return); connect(sendShortcut, SIGNAL(triggered()), buttonBox->button(QDialogButtonBox::Ok), SLOT(animateClick())); mainLayout->addWidget(page); mainLayout->addWidget(buttonBox); } LikeBackDialog::~LikeBackDialog() { } QString LikeBackDialog::introductionText() { QString text = "

    " + i18n("Please provide a brief description of your opinion of %1.", QGuiApplication::applicationDisplayName()) + ' '; QString languagesMessage = ""; if (!m_likeBack->acceptedLocales().isEmpty() && !m_likeBack->acceptedLanguagesMessage().isEmpty()) { languagesMessage = m_likeBack->acceptedLanguagesMessage(); QStringList locales = m_likeBack->acceptedLocales(); for (QStringList::Iterator it = locales.begin(); it != locales.end(); ++it) { QString locale = *it; if (QLocale().language() == QLocale(locale).language()) languagesMessage = ""; } } else { if (!QLocale().language() == QLocale::English) languagesMessage = i18n("Please write in English."); } if (!languagesMessage.isEmpty()) // TODO: Replace the URL with a localized one: text += languagesMessage + ' ' + i18n("You may be able to use an online translation tool.", "https://www.google.com/language_tools?hl=" + QLocale().language()) + ' '; // If both "I Like" and "I Dislike" buttons are shown and one is clicked: if ((m_likeBack->buttons() & LikeBack::Like) && (m_likeBack->buttons() & LikeBack::Dislike)) text += i18n("To make the comments you send more useful in improving this application, try to send the same amount of positive and negative comments.") + " "; if (!(m_likeBack->buttons() & LikeBack::Feature)) text += i18n("Do not ask for new features: your requests will be ignored."); return text; } void LikeBackDialog::ensurePolished() { QDialog::ensurePolished(); m_comment->setFocus(); } void LikeBackDialog::slotDefault() { m_likeBack->askEmailAddress(); } void LikeBackDialog::slotOk() { send(); } void LikeBackDialog::changeButtonBarVisible() { m_likeBack->setUserWantsToShowBar(m_showButtons->isChecked()); } void LikeBackDialog::commentChanged() { QPushButton *sendButton = buttonBox->button(QDialogButtonBox::Ok); sendButton->setEnabled(!m_comment->document()->isEmpty()); } void LikeBackDialog::send() { QString emailAddress = m_likeBack->emailAddress(); QString type = (likeButton->isChecked() ? "Like" : (dislikeButton->isChecked() ? "Dislike" : (bugButton->isChecked() ? "Bug" : "Feature"))); QString data = "protocol=" + QUrl::toPercentEncoding("1.0") + '&' + "type=" + QUrl::toPercentEncoding(type) + '&' + "version=" + QUrl::toPercentEncoding(m_likeBack->aboutData()->version()) + '&' + "locale=" + QUrl::toPercentEncoding(QLocale().languageToString(QLocale().language())) + '&' + "window=" + QUrl::toPercentEncoding(m_windowPath) + '&' + "context=" + QUrl::toPercentEncoding(m_context) + '&' + "comment=" + QUrl::toPercentEncoding(m_comment->toPlainText()) + '&' + "email=" + QUrl::toPercentEncoding(emailAddress); QByteArray dataUtf8 = data.toUtf8(); QBuffer buffer(&dataUtf8); KIO::Integration::AccessManager *http = new KIO::Integration::AccessManager(this); QString urlString; urlString = "https://" + m_likeBack->hostName() + ":" + m_likeBack->hostPort() + m_likeBack->remotePath(); QUrl url(urlString); QNetworkRequest request(url); request.setRawHeader("Content-Type", "application/x-www-form-urlencoded"); qDebug() << "https://" << m_likeBack->hostName() << ":" << m_likeBack->hostPort() << m_likeBack->remotePath(); qDebug() << data; connect(http, SIGNAL(finished(QNetworkReply *)), this, SLOT(requestFinished(QNetworkReply *))); http->post(request, &buffer); m_comment->setEnabled(false); } void LikeBackDialog::requestFinished(QNetworkReply *reply) { // TODO: Save to file if error (connection not present at the moment) m_comment->setEnabled(true); m_likeBack->disableBar(); if (reply->error() != QNetworkReply::NoError) { KMessageBox::error(this, i18n("

    Error while trying to send the report.

    Please retry later.

    "), i18n("Transfer Error")); } else { KMessageBox::information(this, i18n("

    Your comment has been sent successfully. It will help improve the application.

    Thanks for your time.

    "), i18n("Comment Sent")); close(); } m_likeBack->enableBar(); QDialog::accept(); reply->deleteLater(); } diff --git a/src/likeback.h b/src/likeback.h index b5bf61e..1267e32 100644 --- a/src/likeback.h +++ b/src/likeback.h @@ -1,361 +1,348 @@ -/*************************************************************************** - * Copyright (C) 2006 by Sébastien Laoût * - * * - * This program 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 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 Library 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. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2006 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef LIKEBACK_H #define LIKEBACK_H #include class KConfig; class KAboutData; class KActionCollection; class QAction; class QDialogButtonBox; class LikeBackPrivate; /** * @short System to Get Quick Feedback from Beta-Testers * * This system allows users to communicate their liking of the application to its developers. * Thus, developers know what their users prefer of their applications, what should be enhanced, etc. * * Basically, how does it work? * Whenever the user notice something good he appreciate or something he do not like, do not understand, do not find polished... * he can send a few short words to the developers to tell them what he like or do not like. It is only two or three clicks away. * It is fast and efficient. * * This greatly lowers the communication barrier between the application developers and the application users. * It makes the developers understand and satisfy better the needs of the users. * * The LikeBack system has 5 components: * @li In the application: The comment dialog, where the user write a comment, select a type of comment, etc. * @li In the application: The QAction to plug in the Help menu. This action displays the comment dialog. * @li In the application: The button-bar, that floats bellow titlebar of every windows of the application, and let the user to quickly show the comment dialog. * The button-bar can be hidden. * @li On the server: A PHP script that collects every comments that users send. The LikeBack object should be configured to contact that server. * @li On the server: The developer interface. It lists every comments that were sent, let you sort them, add remarks to them, and mark them as fixed or another status. * * Here is an example of code to call to quickly setup LikeBack on the client: * @code * // Instantiate the LikeBack system, and show the first-use information dialog if the button-bar is shown: * LikeBack *likeBack = new LikeBack(LikeBack::AllButtons, LikeBack::isDevelopmentVersion(KGlobal::mainComponent().aboutData->version())); // Show button-bar only in beta-versions * likeBack->setServer("myapp.kde.org", "/likeback/send.php"); * likeBack->setAcceptedLanguages(QStringList::split(";", "en;fr"), i18n("Please write in English or French.")); * * // Comment the following line once you are sure all your windows have a name: * likeBack->setWindowNamesListing(LikeBack::WarnUnnamedWindows); * * // This line should be called early in your KMainWindow constructor because it references actionCollection(). * // It should be called before createGUI() for the action to be plugged in the Help menu: * likeBack->sendACommentAction(actionCollection()); * @endcode * * @see Visit https://basket.kde.org/likeback.php for more information, screenshots, a tutorial, hints, return of experiences, and to download the server-side developer interface... * @author Sebastien Laout */ class LikeBack : public QObject { Q_OBJECT public: /** * Ids of every LikeBack buttons the button-bar can have. * The four first values are each individual buttons you can enable or not. * The next ones are combinations: all buttons at once, and the default set of buttons (Like, Dislike). * Those values are used in the constructor, to set the allowed type of comments, and when triggering the comment dialog, to set the default checked type. * @See The LikeBack constructor and execCommentDialog(). */ enum Button { Like = 0x01, /// The user select that option to report a positive experience he got with the application. Dislike = 0x02, /// The user select that option to report a frustrating experience he got with the application. Bug = 0x04, /// The user select that option to report a bug in the application. Feature = 0x10, /// The user select that option to ask for a new feature he desire. /// If not enabled, the user is explicitly informed she cannot ask for new features. AllButtons = Like | Dislike | Bug | Feature, /// Usable in the constructor to enable every possible buttons. DefaultButtons = Like | Dislike /// Usable in the constructor to enable only the recommended default set of buttons. }; /** * Flags letting LikeBack print out name and path of each window you show during execution, for debugging purpose. * @See The method setWindowNamesListing() explains how to use those values. */ enum WindowListing { NoListing = 0, /// Do not print out any window name. For release time. WarnUnnamedWindows = 1, /// Each time the user option a window, print out a message if the window is unnamed. For development needs, to check windows. AllWindows = 2 /// Print out the window hierarchy of each opened windows during execution. For development needs, to check every windows have an understandable name. }; /** * You only need to call the constructor once, typically in main.cpp. * Even if you do not show the button-bar by default, you should instantiate LikeBack, * to include its action in the Help menu of your application, to let the users send comments or activate the bar. * @param buttons The types of comments you want to get. Determine which radio-buttons are shown in the comment dialog, * and which ones are displayed in the button-bar. Default buttons do not show the Bug and Feature buttons because you are * likely to already have a way to get bug and feature reports (most of the time, it is a bugs.kde.org account). * If you do not have that, then use the value LikeBack::AllButtons to show every possible buttons. * @param showBarByDefault Determines if the floating button-bar should also be shown, in addition to the action in the Help menu. * Advise: to avoid getting too much noise, enable it only if it is a small application or a development release. * Notes: This is only a default value, the user will be able to enable or disabled the bar afterward. * The button-bar display is stored by version. On a new version, your default value will take effect again. * This allow you to disable the button-bar once the version is stable enough to be released as final. * @param config Set the configuration file where to store the user email address and if the button-bar should be shown. * By default (null), the KApplication configuration object is used. * @param aboutData Set the KAboutData instance used to get the application name and version. By default (null), the KApplication about data object is used. * The application name is only used in the first-use information message. * The version is used to store the button-bar visibility per version (can be shown in a development version but not in a final one...) * and to send with the comment, so you can filter per version and know if a comment refers the latest version of the application or not. */ explicit LikeBack(Button buttons = DefaultButtons, bool showBarByDefault = false, KConfig *config = 0, const KAboutData *aboutData = 0); /** * Destructor. * Also hide the button-bar, if it was shown. * Be careful, the QAction is deleted. Do not use it afterward, and take care to unplug it before destroying this LikeBack instance. */ ~LikeBack() override; /** * This method is interesting while setting up the system for the first time. * LikeBack send the current window name (and hierarchy) with the comment. This allows you to put the comments in their context. * So, of course, you are encouraged to give a name to your windows. It is done in the constructor of the widgets. * This method allows to output the name of the current window to the standard output. * So you can use the application, open all the windows, and when you see a warning, you know which window you should assign a name. * @see The WindowListing flags for an enumeration and explaining of every possibilities. * @Note If you do not name your windows, the name of the classes will be sent. So it is not that grave. */ void setWindowNamesListing(WindowListing windowListing); /** * @Returns The window listing flag. * @see setWindowNamesListing() */ WindowListing windowNamesListing(); /** * By default, only English comments are accepted. The user is informed she must write in this language by a sentence placed in the comment dialog. * If you have people talking other languages in your development team, it can be interesting to call this method to define the accepted locales (languages), * and provide a message to inform users. The developer interface on the server let developers view comments in their locale. * Note that no verification is done to check if the user used the right language, it would be impossible. * The list of locales is there to make it possible to NOT show the message for users of the accepted languages. * For instance, if you accept only English and French, and that the application run in a French environment, * it is likely the user is French and will write comments using French. Telling him he should write in French is unnecessary and redundant. * Passing an empty list and an empty string to the method will make LikeBack display the default message telling the user only English is accepted. * Example of call you can quickly copy, paste and adapt: * @code * likeBack->setAcceptedLanguages(QStringList::split(";", "en;fr"), i18n("Please write in English or French.")); * @endcode * @Note During tests, if you do not see the sentence, it is because you are running the application with an "accepted language": do not be surprised ;-) * @param locales The list of locales where the message does not need to be shown. See TODO TODO for a list of available locales for you to choose. * @param message The message to displays to the user to tell him what languages are accepted to write his comments. */ void setAcceptedLanguages(const QStringList &locales, const QString &message); /** * @Returns The list of accepted locales for the user to write comments. * @see setAcceptedLanguages() */ QStringList acceptedLocales(); /** * @Returns The message displayed to users who are not running the application in an accepted locale. * @see setAcceptedLanguages() */ QString acceptedLanguagesMessage(); /** * Set the path where LikeBack should send every comments. * It is composed of the server host name, the path to the PHP script used to send comments, and optionally a port number if it is not 80. * This call is mandatory for LikeBack to work. * @param hostName The server host name to contact when sending comments. For instance "myapp.kde.org". * @param remotePath The path to the send script on the server. For instance, "/likeback/send.php". * @param hostPort Optional port used to contact the server using the HTTP protocol. By default, it is port 80. */ void setServer(const QString &hostName, const QString &remotePath, quint16 hostPort = 80); /** * @Returns The server host name to contact when sending comments. * @see setServer() */ QString hostName(); /** * @Returns The path to the send script on the server. * @see setServer() */ QString remotePath(); /** * @Returns The port used to contact the server using the HTTP protocol. * @see setServer() */ quint16 hostPort(); /** * Get the QAction letting user to show the comment dialog. * You should plug it in your Help menu, just bellow the "Report a Bug" action, or replace it. * Adding the action below "Report a Bug" or replacing "Report a Bug" depends on your application and if you have a Bugzilla account. * If you do not have a Bugzilla account, LikeBack is a good way for your small application to get bug reports: remove "Report a Bug". * For more information about how to configure LikeBack depending on your application size and settings, see the constructor documentation. * @Note The action is named "likeback_send_a_comment". So you should add the following XML in the *ui.rc file of your application: * @code * * @endcode */ QAction *sendACommentAction(KActionCollection *parent = 0); /** * @Returns The path of the currently active window. Each windows are separated with "~~". * Normally, you should not need to call this method since it is used to send the window path. * But if you call execCommentDialog(), you could need to use it. */ static QString activeWindowPath(); /** * @Returns The combination of buttons that are shown in the comment dialog and the button-bar. */ Button buttons(); /** * @Returns true if the button-bar is currently enabled. Ie, if it has been re-enabled as many times as it has been disabled. * @see The method disableBar() for more information on how enabling/disabling works. */ bool enabledBar(); public slots: /** * Temporarily disable the button-bar: it is hidden from the screen if it was shown. * Does not affect anything if the user has not chosen to show the button-bar. * @Note Calls to enableBar() and disableBar() are ref-counted. * This means that the number of times disableBar() is called is memorized, * and enableBar() will only have effect after it has been called as many times as disableBar() was called before. * So, make sure to always call enableBar() the same number of times we called disableBar(). * And please make sure to ALWAYS call disableBar() BEFORE enableBar(). * In the counter-case, another code could call disableBar() and EXCPECT the bar to be disabled. But it will not, because its call only canceled yours. * @Note Sometimes, you will absolutely need to call enableBar() before disableBar(). * For instance, MyWindow::show() calls enableBar() and MyWindow::hide() calls disableBar(). * This is the trick used to show the LikeBack button-bar of a Kontact plugin only when the main widget of that plugin is active. * In this case, call disableBar() at the begin of your program, so the disable count will never be negative. * @Note If the bar is enabled, it does not mean the bar is shown. For that, the developer (using showBarByDefault in the constructor) * or the user (by checking the checkbox in the comment dialog) have to explicitly show the bar. */ void disableBar(); /** * Re-enable the button-bar one time. * @see The method disableBar() for more information on how enabling/disabling works. */ void enableBar(); /** * Show the first-use information dialog telling the user the meaning of the LikeBack system and giving examples of every comment types. */ void showInformationMessage(); /** * Popup the comment dialog. * With no parameter, it popups in the default configuration: the first type is checked, empty message, current window path, and empty context. * You can use the following parameters to customize how it should appears: * @param type Which radiobutton should be checked when popping up. AllButton, the default value, means the first available type will be checked. * @param initialComment The text to put in the comment text area. Allows you to popup the dialog in some special circumstances, * like to let the user report an internal error by populating the comment area with technical details useful for you to debug. * @param windowPath The window path to send with the comment. If empty (the default), the current window path is took. * Separate window names with "~~". For instance "MainWindow~~NewFile~~FileOpen". * If you popup the dialog after an error occurred, you can put the error name in that field (if the window path has no sense in that context). * When the dialog is popuped up from the sendACommentAction() QAction, this value is "HelpMenu", because there is no way to know if the user * is commenting a thing he found/thinked about in a sub-dialog. * @param context Not used for the moment. Will allow more fine-grained application status report. */ void execCommentDialog(Button type = AllButtons, const QString &initialComment = "", const QString &windowPath = "", const QString &context = ""); /** * Popups the dialog for the user to set his email address. * The popup will always be shown, even if the user already provided an email address. */ void askEmailAddress(); private: LikeBackPrivate *d; /** * Get the user email address from KControl. */ void fetchUserEmail(); private slots: /** * Slot triggered by the "Help -> Send a Comment to Developers" QAction. * It popups the comment dialog, and set the window path to "HelpMenuAction", * because current window path has no meaning in that case. */ void execCommentDialogFromHelp(); public: /** * @Returns true if the user has enabled the LikeBack bar for this version. */ bool userWantsToShowBar(); /** * Explicitly set if the floating button-bar should be shown or not. * Theoretically, this choice should only be left to the user, * and to the developers for the default value, already provided in the constructor. */ void setUserWantsToShowBar(bool showBar); /** * @Returns A pointer to the KAboutData used to determine the application name and version. * @See The LikeBack constructor for more information. */ const KAboutData *aboutData(); /** * @Returns A pointer to the KConfig used to store user configuration (email address, if the button-bar should be shown). * @See The LikeBack constructor for more information. */ KConfig *config(); /** * During the first comment sending, the user is invited to enter his email address for the developers to be able to contact him back. * He is only asked once, or he can set or change it by using the bottom-left button in the comment dialog. * @Returns true if the user has already configured his email address. */ bool emailAddressAlreadyProvided(); /** * @Returns The email user address, or ask it to the user if he have not provided or ignored it. * @Returns An empty string if the user cancelled the request dialog. */ QString emailAddress(); /** * Define or re-define the user email address. * LikeBack will not ask it again to the user, unless you set @p userProvided to false. * Then, this call can be considered as setting the default email address, that the user should confirm later. */ void setEmailAddress(const QString &address, bool userProvided = true); /** * @Returns true if @p version is an Alpha, Beta, RC, SVN or CVS version. * You can use this static method in the constructor to enable the button-bar by default only during beta-releases. */ static bool isDevelopmentVersion(const QString &version); }; #endif // LIKEBACK_H diff --git a/src/likeback_p.h b/src/likeback_p.h index 073f8ca..a180805 100644 --- a/src/likeback_p.h +++ b/src/likeback_p.h @@ -1,116 +1,103 @@ -/*************************************************************************** - * Copyright (C) 2006 by Sébastien Laoût * - * * - * This program 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 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 Library 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. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2006 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef LIKEBACK_PRIVATE_H #define LIKEBACK_PRIVATE_H #include "likeback.h" #include #include class QToolButton; class KTextEdit; class QRadioButton; class QCheckBox; class QGroupBox; class QNetworkReply; class Kaction; class LikeBackBar; class LikeBackPrivate { public: LikeBackPrivate(); ~LikeBackPrivate(); LikeBackBar *bar; KConfig *config; const KAboutData *aboutData; LikeBack::Button buttons; QString hostName; QString remotePath; quint16 hostPort; QStringList acceptedLocales; QString acceptedLanguagesMessage; LikeBack::WindowListing windowListing; bool showBarByDefault; bool showBar; int disabledCount; QString fetchedEmail; QAction *action; }; class LikeBackBar : public QWidget { Q_OBJECT public: explicit LikeBackBar(LikeBack *likeBack); ~LikeBackBar() override; public slots: void startTimer(); void stopTimer(); private slots: void autoMove(); void clickedLike(); void clickedDislike(); void clickedBug(); void clickedFeature(); private: LikeBack *m_likeBack; QTimer m_timer; QToolButton *m_likeButton; QToolButton *m_dislikeButton; QToolButton *m_bugButton; QToolButton *m_featureButton; }; class LikeBackDialog : public QDialog { Q_OBJECT public: LikeBackDialog(LikeBack::Button reason, const QString &initialComment, const QString &windowPath, const QString &context, LikeBack *likeBack); ~LikeBackDialog() override; private: LikeBack *m_likeBack; QString m_windowPath; QString m_context; KTextEdit *m_comment; QRadioButton *likeButton; QRadioButton *dislikeButton; QRadioButton *bugButton; QRadioButton *featureButton; QCheckBox *m_showButtons; QDialogButtonBox *buttonBox; QString introductionText(); private slots: void ensurePolished(); void slotDefault(); void slotOk(); void changeButtonBarVisible(); void commentChanged(); void send(); void requestFinished(QNetworkReply *reply); }; #endif // LIKEBACK_PRIVATE_H diff --git a/src/linklabel.cpp b/src/linklabel.cpp index e8eeed4..ed31c36 100644 --- a/src/linklabel.cpp +++ b/src/linklabel.cpp @@ -1,744 +1,731 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "linklabel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "global.h" #include "htmlexporter.h" #include "kcolorcombo2.h" #include "tools.h" #include "variouswidgets.h" /** LinkLook */ LinkLook *LinkLook::soundLook = new LinkLook(/*useLinkColor=*/false, /*canPreview=*/false); LinkLook *LinkLook::fileLook = new LinkLook(/*useLinkColor=*/false, /*canPreview=*/true); LinkLook *LinkLook::localLinkLook = new LinkLook(/*useLinkColor=*/true, /*canPreview=*/true); LinkLook *LinkLook::networkLinkLook = new LinkLook(/*useLinkColor=*/true, /*canPreview=*/false); LinkLook *LinkLook::launcherLook = new LinkLook(/*useLinkColor=*/true, /*canPreview=*/false); LinkLook *LinkLook::crossReferenceLook = new LinkLook(/*useLinkColor=*/true, /*canPreview=*/false); LinkLook::LinkLook(bool useLinkColor, bool canPreview) { m_useLinkColor = useLinkColor; m_canPreview = canPreview; m_iconSize = 0; } LinkLook::LinkLook(const LinkLook &other) { m_useLinkColor = other.useLinkColor(); m_canPreview = other.canPreview(); setLook(other.italic(), other.bold(), other.underlining(), other.color(), other.hoverColor(), other.iconSize(), other.preview()); } void LinkLook::setLook(bool italic, bool bold, int underlining, QColor color, QColor hoverColor, int iconSize, int preview) { m_italic = italic; m_bold = bold; m_underlining = underlining; m_color = color; m_hoverColor = hoverColor; m_iconSize = iconSize; m_preview = (canPreview() ? preview : None); } int LinkLook::previewSize() const { if (previewEnabled()) { switch (preview()) { default: case None: return 0; case IconSize: return iconSize(); case TwiceIconSize: return iconSize() * 2; case ThreeIconSize: return iconSize() * 3; } } else return 0; } QColor LinkLook::effectiveColor() const { if (m_color.isValid()) return m_color; else return defaultColor(); } QColor LinkLook::effectiveHoverColor() const { if (m_hoverColor.isValid()) return m_hoverColor; else return defaultHoverColor(); } QColor LinkLook::defaultColor() const { if (m_useLinkColor) return qApp->palette().color(QPalette::Link); else return qApp->palette().color(QPalette::Text); } QColor LinkLook::defaultHoverColor() const { return Qt::red; } LinkLook *LinkLook::lookForURL(const QUrl &url) { return url.isLocalFile() ? localLinkLook : networkLinkLook; } QString LinkLook::toCSS(const QString &cssClass, const QColor &defaultTextColor) const { // Set the link class: QString css = QString("{ display: block; width: 100%;"); if (underlineOutside()) css += " text-decoration: underline;"; else css += " text-decoration: none;"; if (m_italic == true) css += " font-style: italic;"; if (m_bold == true) css += " font-weight: bold;"; QColor textColor = (color().isValid() || m_useLinkColor ? effectiveColor() : defaultTextColor); css += QString(" color: %1; }\n").arg(textColor.name()); QString css2 = css; css.prepend(QString(" .%1 a").arg(cssClass)); css2.prepend(QString(" a.%1").arg(cssClass)); // Set the hover state class: QString hover; if (m_underlining == OnMouseHover) hover = "text-decoration: underline;"; else if (m_underlining == OnMouseOutside) hover = "text-decoration: none;"; if (effectiveHoverColor() != effectiveColor()) { if (!hover.isEmpty()) hover += ' '; hover += QString("color: %4;").arg(effectiveHoverColor().name()); } // But include it only if it contain a different style than non-hover state: if (!hover.isEmpty()) { css += QString(" .%1 a:hover { %2 }\n").arg(cssClass, hover); css2 += QString(" a:hover.%1 { %2 }\n").arg(cssClass, hover); } return css + css2; } /** LinkLabel */ LinkLabel::LinkLabel(int hAlign, int vAlign, QWidget *parent, Qt::WindowFlags f) : QFrame(parent, f) , m_isSelected(false) , m_isHovered(false) , m_look(0) { initLabel(hAlign, vAlign); } LinkLabel::LinkLabel(const QString &title, const QString &icon, LinkLook *look, int hAlign, int vAlign, QWidget *parent, Qt::WindowFlags f) : QFrame(parent, f) , m_isSelected(false) , m_isHovered(false) , m_look(0) { initLabel(hAlign, vAlign); setLink(title, icon, look); } void LinkLabel::initLabel(int hAlign, int vAlign) { m_layout = new QBoxLayout(QBoxLayout::LeftToRight, this); m_icon = new QLabel(this); m_title = new QLabel(this); m_spacer1 = new QSpacerItem(0, 0, QSizePolicy::Preferred /*Expanding*/, QSizePolicy::Preferred /*Expanding*/); m_spacer2 = new QSpacerItem(0, 0, QSizePolicy::Preferred /*Expanding*/, QSizePolicy::Preferred /*Expanding*/); m_hAlign = hAlign; m_vAlign = vAlign; m_title->setTextFormat(Qt::PlainText); // DEGUB: // m_icon->setPaletteBackgroundColor("lightblue"); // m_title->setPaletteBackgroundColor("lightyellow"); } LinkLabel::~LinkLabel() { } void LinkLabel::setLink(const QString &title, const QString &icon, LinkLook *look) { if (look) m_look = look; // Needed for icon size m_title->setText(title); m_title->setVisible(!title.isEmpty()); if (icon.isEmpty()) m_icon->clear(); else { QPixmap pixmap = DesktopIcon(icon, m_look->iconSize(), m_look->iconSize()); if (!pixmap.isNull()) m_icon->setPixmap(pixmap); } m_icon->setVisible(!icon.isEmpty()); if (look) setLook(look); } void LinkLabel::setLook(LinkLook *look) // FIXME: called externally (so, without setLink()) it's buggy (icon not { m_look = look; QFont font; font.setBold(look->bold()); font.setUnderline(look->underlineOutside()); font.setItalic(look->italic()); m_title->setFont(font); QPalette palette; if (m_isSelected) palette.setColor(m_title->foregroundRole(), QApplication::palette().color(QPalette::Text)); else palette.setColor(m_title->foregroundRole(), look->effectiveColor()); m_title->setPalette(palette); m_icon->setVisible(m_icon->pixmap() && !m_icon->pixmap()->isNull()); setAlign(m_hAlign, m_vAlign); } void LinkLabel::setAlign(int hAlign, int vAlign) { m_hAlign = hAlign; m_vAlign = vAlign; if (!m_look) return; // Define alignment flags : Qt::Alignment hFlag, vFlag; switch (hAlign) { default: case 0: hFlag = Qt::AlignLeft; break; case 1: hFlag = Qt::AlignHCenter; break; case 2: hFlag = Qt::AlignRight; break; } switch (vAlign) { case 0: vFlag = Qt::AlignTop; break; default: case 1: vFlag = Qt::AlignVCenter; break; case 2: vFlag = Qt::AlignBottom; break; } // Clear the widget : m_layout->removeItem(m_spacer1); m_layout->removeWidget(m_icon); m_layout->removeWidget(m_title); m_layout->removeItem(m_spacer2); // Otherwise, minimumSize will be incoherent (last size ? ) m_layout->setSizeConstraint(QLayout::SetMinimumSize); // And re-populate the widget with the appropriates things and order bool addSpacers = (hAlign == 1); m_layout->setDirection(QBoxLayout::LeftToRight); // m_title->setSizePolicy( QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Maximum/*Expanding*/, 0, 0, false) ); m_icon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); m_spacer1->changeSize(QSizePolicy::Expanding, QSizePolicy::Preferred); m_spacer2->changeSize(QSizePolicy::Expanding, QSizePolicy::Preferred); m_icon->setAlignment(hFlag | vFlag); m_title->setAlignment(hFlag | vFlag); if (hAlign) m_title->setWordWrap(true); if ((addSpacers && (vAlign != 0)) || (m_title->text().isEmpty() && hAlign == 2)) m_layout->addItem(m_spacer1); if (hAlign == 2) { // If align at right, icon is at right m_layout->addWidget(m_title); m_layout->addWidget(m_icon); } else { m_layout->addWidget(m_icon); m_layout->addWidget(m_title); } if ((addSpacers && (vAlign != 2)) || (m_title->text().isEmpty() && hAlign == 0)) m_layout->addItem(m_spacer2); } void LinkLabel::enterEvent(QEvent *) { m_isHovered = true; if (!m_isSelected) { QPalette palette; palette.setColor(m_title->foregroundRole(), m_look->effectiveHoverColor()); m_title->setPalette(palette); } QFont font = m_title->font(); font.setUnderline(m_look->underlineInside()); m_title->setFont(font); } void LinkLabel::leaveEvent(QEvent *) { m_isHovered = false; if (!m_isSelected) { QPalette palette; palette.setColor(m_title->foregroundRole(), m_look->effectiveColor()); m_title->setPalette(palette); } QFont font = m_title->font(); font.setUnderline(m_look->underlineOutside()); m_title->setFont(font); } void LinkLabel::setSelected(bool selected) { m_isSelected = selected; QPalette palette; if (selected) palette.setColor(m_title->foregroundRole(), QApplication::palette().color(QPalette::HighlightedText)); else if (m_isHovered) palette.setColor(m_title->foregroundRole(), m_look->effectiveHoverColor()); else palette.setColor(m_title->foregroundRole(), m_look->effectiveColor()); m_title->setPalette(palette); } void LinkLabel::setPaletteBackgroundColor(const QColor &color) { QPalette framePalette; framePalette.setColor(QFrame::foregroundRole(), color); QFrame::setPalette(framePalette); QPalette titlePalette; titlePalette.setColor(m_title->foregroundRole(), color); m_title->setPalette(titlePalette); } int LinkLabel::heightForWidth(int w) const { int iconS = (m_icon->isVisible()) ? m_look->iconSize() : 0; // Icon size int iconW = iconS; // Icon width to remove to w int titleH = (m_title->isVisible()) ? m_title->heightForWidth(w - iconW) : 0; // Title height return (titleH >= iconS) ? titleH : iconS; // No margin for the moment ! } /** class LinkDisplay */ LinkDisplay::LinkDisplay() : m_title() , m_icon() , m_preview() , m_look(0) , m_font() , m_minWidth(0) , m_width(0) , m_height(0) { } void LinkDisplay::setLink(const QString &title, const QString &icon, LinkLook *look, const QFont &font) { setLink(title, icon, m_preview, look, font); } void LinkDisplay::setLink(const QString &title, const QString &icon, const QPixmap &preview, LinkLook *look, const QFont &font) { m_title = title; m_icon = icon; m_preview = preview; m_look = look; m_font = font; // "Constants": int BUTTON_MARGIN = qApp->style()->pixelMetric(QStyle::PM_ButtonMargin); int LINK_MARGIN = BUTTON_MARGIN + 2; // Recompute m_minWidth: QRect textRect = QFontMetrics(labelFont(font, false)).boundingRect(0, 0, /*width=*/1, 500000, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, m_title); int iconPreviewWidth = qMax(m_look->iconSize(), (m_look->previewEnabled() ? m_preview.width() : 0)); m_minWidth = BUTTON_MARGIN - 1 + iconPreviewWidth + LINK_MARGIN + textRect.width(); // Recompute m_maxWidth: textRect = QFontMetrics(labelFont(font, false)).boundingRect(0, 0, /*width=*/50000000, 500000, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, m_title); m_maxWidth = BUTTON_MARGIN - 1 + iconPreviewWidth + LINK_MARGIN + textRect.width(); // Adjust m_width: if (m_width < m_minWidth) setWidth(m_minWidth); // Recompute m_height: m_height = heightForWidth(m_width); } void LinkDisplay::setWidth(qreal width) { if (width < m_minWidth) width = m_minWidth; if (width != m_width) { m_width = width; m_height = heightForWidth(m_width); } } /** Paint on @p painter * in (@p x, @p y, @p width, @p height) * using @p palette for the button drawing (if @p isHovered) * and the LinkLook color() for the text, * unless [the LinkLook !color.isValid() and it does not useLinkColor()] or [@p isDefaultColor is false]: in this case it will use @p palette's active text color. * It will draw the button if @p isIconButtonHovered. */ void LinkDisplay::paint(QPainter *painter, qreal x, qreal y, qreal width, qreal height, const QPalette &palette, bool isDefaultColor, bool isSelected, bool isHovered, bool isIconButtonHovered) const { qreal BUTTON_MARGIN = qApp->style()->pixelMetric(QStyle::PM_ButtonMargin); qreal LINK_MARGIN = BUTTON_MARGIN + 2; QPixmap pixmap; // Load the preview...: if (!isHovered && m_look->previewEnabled() && !m_preview.isNull()) pixmap = m_preview; // ... Or the icon (if no preview or if the "Open" icon should be shown): else { qreal iconSize = m_look->iconSize(); QString iconName = (isHovered ? Global::openNoteIcon() : m_icon); KIconLoader::States iconState = (isIconButtonHovered ? KIconLoader::ActiveState : KIconLoader::DefaultState); pixmap = KIconLoader::global()->loadIcon(iconName, KIconLoader::Desktop, iconSize, iconState, QStringList(), 0L, /*canReturnNull=*/false); } qreal iconPreviewWidth = qMax(m_look->iconSize(), (m_look->previewEnabled() ? m_preview.width() : 0)); qreal pixmapX = (iconPreviewWidth - pixmap.width()) / 2; qreal pixmapY = (height - pixmap.height()) / 2; // Draw the button (if any) and the icon: if (isHovered) { QStyleOption opt; opt.rect = QRect(-1, -1, iconPreviewWidth + 2 * BUTTON_MARGIN, height + 2); opt.state = isIconButtonHovered ? (QStyle::State_MouseOver | QStyle::State_Enabled) : QStyle::State_Enabled; qApp->style()->drawPrimitive(QStyle::PE_PanelButtonCommand, &opt, painter); } painter->drawPixmap(x + BUTTON_MARGIN - 1 + pixmapX, y + pixmapY, pixmap); // Figure out the text color: if (isSelected) { painter->setPen(qApp->palette().color(QPalette::HighlightedText)); } else if (isIconButtonHovered) painter->setPen(m_look->effectiveHoverColor()); else if (!isDefaultColor || (!m_look->color().isValid() && !m_look->useLinkColor())) // If the color is FORCED or if the link color default to the text color: painter->setPen(palette.color(QPalette::Active, QPalette::WindowText)); else painter->setPen(m_look->effectiveColor()); // Draw the text: painter->setFont(labelFont(m_font, isIconButtonHovered)); painter->drawText(x + BUTTON_MARGIN - 1 + iconPreviewWidth + LINK_MARGIN, y, width - BUTTON_MARGIN + 1 - iconPreviewWidth - LINK_MARGIN, height, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, m_title); } QPixmap LinkDisplay::feedbackPixmap(qreal width, qreal height, const QPalette &palette, bool isDefaultColor) { qreal theWidth = qMin(width, maxWidth()); qreal theHeight = qMin(height, heightForWidth(theWidth)); QPixmap pixmap(theWidth, theHeight); pixmap.fill(palette.color(QPalette::Active, QPalette::Background)); QPainter painter(&pixmap); paint(&painter, 0, 0, theWidth, theHeight, palette, isDefaultColor, /*isSelected=*/false, /*isHovered=*/false, /*isIconButtonHovered=*/false); painter.end(); return pixmap; } bool LinkDisplay::iconButtonAt(const QPointF &pos) const { qreal BUTTON_MARGIN = qApp->style()->pixelMetric(QStyle::PM_ButtonMargin); // int LINK_MARGIN = BUTTON_MARGIN + 2; qreal iconPreviewWidth = qMax(m_look->iconSize(), (m_look->previewEnabled() ? m_preview.width() : 0)); return pos.x() <= BUTTON_MARGIN - 1 + iconPreviewWidth + BUTTON_MARGIN; } QRectF LinkDisplay::iconButtonRect() const { qreal BUTTON_MARGIN = qApp->style()->pixelMetric(QStyle::PM_ButtonMargin); // int LINK_MARGIN = BUTTON_MARGIN + 2; qreal iconPreviewWidth = qMax(m_look->iconSize(), (m_look->previewEnabled() ? m_preview.width() : 0)); return QRectF(0, 0, BUTTON_MARGIN - 1 + iconPreviewWidth + BUTTON_MARGIN, m_height); } QFont LinkDisplay::labelFont(QFont font, bool isIconButtonHovered) const { if (m_look->italic()) font.setItalic(true); if (m_look->bold()) font.setBold(true); if (isIconButtonHovered) { if (m_look->underlineInside()) font.setUnderline(true); } else { if (m_look->underlineOutside()) font.setUnderline(true); } return font; } qreal LinkDisplay::heightForWidth(qreal width) const { qreal BUTTON_MARGIN = qApp->style()->pixelMetric(QStyle::PM_ButtonMargin); qreal LINK_MARGIN = BUTTON_MARGIN + 2; qreal iconPreviewWidth = qMax(m_look->iconSize(), (m_look->previewEnabled() ? m_preview.width() : 0)); qreal iconPreviewHeight = qMax(m_look->iconSize(), (m_look->previewEnabled() ? m_preview.height() : 0)); QRectF textRect = QFontMetrics(labelFont(m_font, false)).boundingRect(0, 0, width - BUTTON_MARGIN + 1 - iconPreviewWidth - LINK_MARGIN, 500000, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, m_title); return qMax(textRect.height(), iconPreviewHeight + 2 * BUTTON_MARGIN - 2); } QString LinkDisplay::toHtml(const QString & /*imageName*/) const { // TODO return ""; } QString LinkDisplay::toHtml(HTMLExporter *exporter, const QUrl &url, const QString &title) { QString linkIcon; if (m_look->previewEnabled() && !m_preview.isNull()) { QString fileName = Tools::fileNameForNewFile("preview_" + url.fileName() + ".png", exporter->iconsFolderPath); QString fullPath = exporter->iconsFolderPath + fileName; m_preview.save(fullPath, "PNG"); linkIcon = QString("\"\"").arg(exporter->iconsFolderName + fileName, QString::number(m_preview.width()), QString::number(m_preview.height())); } else { linkIcon = exporter->iconsFolderName + exporter->copyIcon(m_icon, m_look->iconSize()); linkIcon = QString("\"\"").arg(linkIcon, QString::number(m_look->iconSize()), QString::number(m_look->iconSize())); } QString linkTitle = Tools::textToHTMLWithoutP(title.isEmpty() ? m_title : title); return QString("%2 %3").arg(url.toDisplayString(), linkIcon, linkTitle); } /** LinkLookEditWidget **/ LinkLookEditWidget::LinkLookEditWidget(KCModule *module, const QString exTitle, const QString exIcon, QWidget *parent, Qt::WindowFlags fl) : QWidget(parent, fl) { QLabel *label; QVBoxLayout *layout = new QVBoxLayout(this); m_italic = new QCheckBox(i18n("I&talic"), this); layout->addWidget(m_italic); m_bold = new QCheckBox(i18n("&Bold"), this); layout->addWidget(m_bold); QGridLayout *gl = new QGridLayout; layout->addLayout(gl); gl->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding), 1, /*2*/ 3); m_underlining = new KComboBox(this); m_underlining->addItem(i18n("Always")); m_underlining->addItem(i18n("Never")); m_underlining->addItem(i18n("On mouse hovering")); m_underlining->addItem(i18n("When mouse is outside")); label = new QLabel(this); label->setText(i18n("&Underline:")); label->setBuddy(m_underlining); gl->addWidget(label, 0, 0); gl->addWidget(m_underlining, 0, 1); m_color = new KColorCombo2(QRgb(), this); label = new QLabel(this); label->setText(i18n("Colo&r:")); label->setBuddy(m_color); gl->addWidget(label, 1, 0); gl->addWidget(m_color, 1, 1); m_hoverColor = new KColorCombo2(QRgb(), this); label = new QLabel(this); label->setText(i18n("&Mouse hover color:")); label->setBuddy(m_hoverColor); gl->addWidget(label, 2, 0); gl->addWidget(m_hoverColor, 2, 1); QHBoxLayout *icoLay = new QHBoxLayout(0); m_iconSize = new IconSizeCombo(this); icoLay->addWidget(m_iconSize); label = new QLabel(this); label->setText(i18n("&Icon size:")); label->setBuddy(m_iconSize); gl->addWidget(label, 3, 0); gl->addItem(icoLay, 3, 1); m_preview = new KComboBox(this); m_preview->addItem(i18n("None")); m_preview->addItem(i18n("Icon size")); m_preview->addItem(i18n("Twice the icon size")); m_preview->addItem(i18n("Three times the icon size")); m_label = new QLabel(this); m_label->setText(i18n("&Preview:")); m_label->setBuddy(m_preview); m_hLabel = new HelpLabel(i18n("You disabled preview but still see images?"), i18n("

    This is normal because there are several type of notes.
    " "This setting only applies to file and local link notes.
    " "The images you see are image notes, not file notes.
    " "File notes are generic documents, whereas image notes are pictures you can draw in.

    " "

    When dropping files to baskets, %1 detects their type and shows you the content of the files.
    " "For instance, when dropping image or text files, image and text notes are created for them.
    " "For type of files %2 does not understand, they are shown as generic file notes with just an icon or file preview and a filename.

    " "

    If you do not want the application to create notes depending on the content of the files you drop, " "go to the \"General\" page and uncheck \"Image or animation\" in the \"View Content of Added Files for the Following Types\" group.

    ", // TODO: Note: you can resize down maximum size of images... QGuiApplication::applicationDisplayName(), QGuiApplication::applicationDisplayName()), this); gl->addWidget(m_label, 4, 0); gl->addWidget(m_preview, 4, 1); gl->addWidget(m_hLabel, 5, 1, 1, 2); QGroupBox *gb = new QGroupBox(i18n("Example"), this); QHBoxLayout *gbLayout = new QHBoxLayout; gb->setLayout(gbLayout); m_exLook = new LinkLook; m_example = new LinkLabel(exTitle, exIcon, m_exLook, 1, 1); gbLayout->addWidget(m_example); m_example->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_example->setCursor(QCursor(Qt::PointingHandCursor)); layout->addWidget(gb); m_exTitle = exTitle; m_exIcon = exIcon; connect(m_italic, SIGNAL(stateChanged(int)), this, SLOT(slotChangeLook())); connect(m_bold, SIGNAL(stateChanged(int)), this, SLOT(slotChangeLook())); connect(m_underlining, SIGNAL(activated(int)), this, SLOT(slotChangeLook())); connect(m_color, SIGNAL(activated(int)), this, SLOT(slotChangeLook())); connect(m_hoverColor, SIGNAL(activated(int)), this, SLOT(slotChangeLook())); connect(m_iconSize, SIGNAL(activated(int)), this, SLOT(slotChangeLook())); connect(m_preview, SIGNAL(activated(int)), this, SLOT(slotChangeLook())); connect(m_italic, SIGNAL(stateChanged(int)), module, SLOT(changed())); connect(m_bold, SIGNAL(stateChanged(int)), module, SLOT(changed())); connect(m_underlining, SIGNAL(activated(int)), module, SLOT(changed())); connect(m_color, SIGNAL(activated(int)), module, SLOT(changed())); connect(m_hoverColor, SIGNAL(activated(int)), module, SLOT(changed())); connect(m_iconSize, SIGNAL(activated(int)), module, SLOT(changed())); connect(m_preview, SIGNAL(activated(int)), module, SLOT(changed())); } void LinkLookEditWidget::set(LinkLook *look) { m_look = look; m_italic->setChecked(look->italic()); m_bold->setChecked(look->bold()); m_underlining->setCurrentIndex(look->underlining()); m_preview->setCurrentIndex(look->preview()); m_color->setDefaultColor(m_look->defaultColor()); m_color->setColor(m_look->color()); m_hoverColor->setDefaultColor(m_look->defaultHoverColor()); m_hoverColor->setColor(m_look->hoverColor()); m_iconSize->setSize(look->iconSize()); m_exLook = new LinkLook(*look); m_example->setLook(m_exLook); if (!look->canPreview()) { m_label->setEnabled(false); m_hLabel->setEnabled(false); m_preview->setEnabled(false); } slotChangeLook(); } void LinkLookEditWidget::slotChangeLook() { saveToLook(m_exLook); m_example->setLink(m_exTitle, m_exIcon, m_exLook); // and can't reload it at another size } LinkLookEditWidget::~LinkLookEditWidget() { } void LinkLookEditWidget::saveChanges() { saveToLook(m_look); } void LinkLookEditWidget::saveToLook(LinkLook *look) { look->setLook(m_italic->isChecked(), m_bold->isChecked(), m_underlining->currentIndex(), m_color->color(), m_hoverColor->color(), m_iconSize->iconSize(), (look->canPreview() ? m_preview->currentIndex() : LinkLook::None)); } diff --git a/src/linklabel.h b/src/linklabel.h index 2798c45..ab3cf73 100644 --- a/src/linklabel.h +++ b/src/linklabel.h @@ -1,261 +1,248 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef LINKLABEL_H #define LINKLABEL_H #include class QPixmap; class QString; class QUrl; class QColor; class QLabel; class QBoxLayout; class QSpacerItem; class QPushButton; class QCheckBox; class QEvent; class KComboBox; class KColorCombo2; class IconSizeCombo; class HTMLExporter; class HelpLabel; class KCModule; /** Store the style of links * @author Sébastien Laoût */ class LinkLook { public: enum Underlining { Always = 0, Never, OnMouseHover, OnMouseOutside }; enum Preview { None = 0, IconSize, TwiceIconSize, ThreeIconSize }; explicit LinkLook(bool useLinkColor = true, bool canPreview = true); LinkLook(const LinkLook &other); void setLook(bool italic, bool bold, int underlining, QColor color, QColor hoverColor, int iconSize, int preview /*= None*/); inline bool italic() const { return m_italic; } inline bool bold() const { return m_bold; } inline int underlining() const { return m_underlining; } inline QColor color() const { return m_color; } inline QColor hoverColor() const { return m_hoverColor; } inline int iconSize() const { return m_iconSize; } inline int preview() const { return m_preview; } inline bool useLinkColor() const { return m_useLinkColor; } inline bool canPreview() const { return m_canPreview; } /* Helping Functions */ bool underlineOutside() const { return underlining() == Always || underlining() == OnMouseOutside; } bool underlineInside() const { return underlining() == Always || underlining() == OnMouseHover; } bool previewEnabled() const { return canPreview() && preview() > None; } int previewSize() const; QColor effectiveColor() const; QColor effectiveHoverColor() const; QColor defaultColor() const; QColor defaultHoverColor() const; QString toCSS(const QString &cssClass, const QColor &defaultTextColor) const; private: bool m_italic; bool m_bold; int m_underlining; QColor m_color; QColor m_hoverColor; int m_iconSize; int m_preview; bool m_useLinkColor; bool m_canPreview; public: /* Global Looks */ static LinkLook *soundLook; static LinkLook *fileLook; static LinkLook *localLinkLook; static LinkLook *networkLinkLook; static LinkLook *launcherLook; static LinkLook *crossReferenceLook; /* Static method to get a LinkLook from an URL */ static LinkLook *lookForURL(const QUrl &url); }; /** Used to represent links with icon and specific look * Note : This label will appear blank while LinkLook willn't be set * @author Sébastien Laoût */ class LinkLabel : public QFrame { Q_OBJECT public: LinkLabel(int hAlign, int vAlign, QWidget *parent = 0, Qt::WindowFlags f = 0); LinkLabel(const QString &title, const QString &icon, LinkLook *look, int hAlign, int vAlign, QWidget *parent = 0, Qt::WindowFlags f = 0); ~LinkLabel() override; public: void setLink(const QString &title, const QString &icon, LinkLook *look = 0); void setLook(LinkLook *look); void setAlign(int hAlign, int vAlign); void setSelected(bool selected); void setPaletteBackgroundColor(const QColor &color); int heightForWidth(int w = -1) const override; protected: void initLabel(int hAlign, int vAlign); void enterEvent(QEvent *) override; void leaveEvent(QEvent *) override; private: QBoxLayout *m_layout; QLabel *m_icon; QLabel *m_title; QSpacerItem *m_spacer1; QSpacerItem *m_spacer2; bool m_isSelected; bool m_isHovered; LinkLook *m_look; int m_hAlign; int m_vAlign; }; /** THE NEW CLASS TO DISPLAY Links FOR THE NEW BASKET ENGINE. * We should get ride of class LinkLabel soon. * And LinkLabel will be entirely rewritten to use this LinkDisplay as the drawing primitives. * @author Sébastien Laoût */ class LinkDisplay { public: LinkDisplay(); /// << Create a new empty unselected LinkDisplay. Please then call setLink() to make sense. // Configure the link displayer: void setLink(const QString &title, const QString &icon, LinkLook *look, const QFont &font); /// << Change the content and disposition. minWidth(), width() & height() can have changed. Keep the old preview (if any) void setLink(const QString &title, const QString &icon, const QPixmap &preview, LinkLook *look, const QFont &font); /// << Idem but change the preview too (or remove it if it is invalid) void setWidth(qreal width); /// << Set a new width. @see height() that will be computed. // Get its properties: qreal minWidth() const { return m_minWidth; } /// << @return the minimum width to display this link. qreal maxWidth() const { return m_maxWidth; } /// << @return the maximum width to display this link. qreal width() const { return m_width; } /// << @return the width of the link. It is never less than minWidth()! qreal height() const { return m_height; } /// << @return the height if the link after having set it a width. // And finally, use it: void paint(QPainter *painter, qreal x, qreal y, qreal width, qreal height, const QPalette &palette, bool isDefaultColor, bool isSelected, bool isHovered, bool isIconButtonHovered) const; /// << Draw the link on a painter. Set textColor to be !isValid() to use the LinkLook color. Otherwise it will use this color! QPixmap feedbackPixmap(qreal width, qreal height, const QPalette &palette, bool isDefaultColor); /// << @return the pixmap to put under the cursor while dragging this object. // Eventually get some information about the link display: bool iconButtonAt(const QPointF &pos) const; /// << @return true if the icon button is under point @p pos. QRectF iconButtonRect() const; /// << @return the rectangle of the icon button. // Utility function: QFont labelFont(QFont font, bool isIconButtonHovered) const; /// << @return the font for this link, according to parent font AND LinkLook! qreal heightForWidth(qreal width) const; /// << @return the needed height to display the link in function of a width. QString toHtml(const QString &imageName) const; /// << Convert the link to HTML code, using the LinkLook to style it. QString toHtml(HTMLExporter *exporter, const QUrl &url, const QString &title = ""); private: QString m_title; QString m_icon; QPixmap m_preview; LinkLook *m_look; QFont m_font; qreal m_minWidth; qreal m_maxWidth; qreal m_width; qreal m_height; }; /** A widget to edit a LinkLook, showing a live example to the user. * @author Sébastien Laoût */ class LinkLookEditWidget : public QWidget { Q_OBJECT public: LinkLookEditWidget(KCModule *module, const QString exTitle, const QString exIcon, QWidget *parent = 0, Qt::WindowFlags fl = 0); ~LinkLookEditWidget() override; void saveChanges(); void saveToLook(LinkLook *look); void set(LinkLook *look); private slots: void slotChangeLook(); protected: LinkLook *m_look; QCheckBox *m_italic; QCheckBox *m_bold; KComboBox *m_underlining; KColorCombo2 *m_color; KColorCombo2 *m_hoverColor; IconSizeCombo *m_iconSize; KComboBox *m_preview; LinkLook *m_exLook; LinkLabel *m_example; QString m_exTitle; QString m_exIcon; HelpLabel *m_hLabel; QLabel *m_label; }; #endif // LINKLABEL_H diff --git a/src/main.cpp b/src/main.cpp index e6ca1b6..ed92fcc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,89 +1,76 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "basket_options.h" #include #include #include #include // TMP IN ALPHA 1 #include "application.h" #include "backup.h" #include "global.h" #include "kde4_migration.h" #include "mainwindow.h" #include "settings.h" int main(int argc, char *argv[]) { const char *argv0 = (argc >= 1 ? argv[0] : ""); Global::commandLineOpts = new QCommandLineParser(); Application app(argc, argv); QCommandLineParser *opts = Global::commandLineOpts; KAboutData::applicationData().setupCommandLine(opts); //--author, --license setupCmdLineOptions(opts); opts->process(app); KAboutData::applicationData().processCommandLine(opts); // show author, license information and exit { Kde4Migrator migrator; if (migrator.migrateKde4Data()) migrator.showPostMigrationDialog(); } app.tryLoadFile(opts->positionalArguments(), QDir::currentPath()); // Initialize the config file Global::basketConfig = KSharedConfig::openConfig("basketrc"); Backup::figureOutBinaryPath(argv0, app); /* Main Window */ MainWindow *win = new MainWindow(); Global::mainWnd = win; Global::bnpView->handleCommandLine(); app.setActiveWindow(win); if (Settings::useSystray()) { // The user wanted to not show the window (but it is already hidden by default, so we do nothing): if (opts->isSet(QCommandLineOption("start-hidden"))) ; // When the application is restored by the desktop session, restore its state: else if (app.isSessionRestored()) { if (!Settings::startDocked()) win->show(); } // Else, the application has been launched explicitly by the user (QMenu, keyboard shortcut...), so he need it, we show it: else win->show(); } else // No system tray icon: always show: win->show(); // Self-test of the presence of basketui.rc (the only required file after basket executable) if (Global::bnpView->popupMenu("basket") == 0L) // An error message will be show by BNPView::popupMenu() return 1; /* Go */ int result = app.exec(); exit(result); // Do not clean up memory to not crash while deleting the QApplication, or do not hang up on session exit } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index db95fab..e77d0ee 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,290 +1,277 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "basketstatusbar.h" #include "bnpview.h" #include "global.h" #include "settings.h" /** Container */ MainWindow::MainWindow(QWidget *parent) : KXmlGuiWindow(parent) , m_settings(0) , m_quit(false) { BasketStatusBar *bar = new BasketStatusBar(statusBar()); m_baskets = new BNPView(this, "BNPViewApp", this, actionCollection(), bar); setCentralWidget(m_baskets); setupActions(); statusBar()->show(); statusBar()->setSizeGripEnabled(true); setAutoSaveSettings(/*groupName=*/QString::fromLatin1("MainWindow"), /*saveWindowSize=*//*FIXME:false:Why was it false??*/ true); // m_actShowToolbar->setChecked( toolBar()->isVisible() ); m_actShowStatusbar->setChecked(statusBar()->isVisible()); connect(m_baskets, SIGNAL(setWindowCaption(const QString &)), this, SLOT(setWindowTitle(const QString &))); // InlineEditors::instance()->richTextToolBar(); setStandardToolBarMenuEnabled(true); createGUI("basketui.rc"); KConfigGroup group = KSharedConfig::openConfig()->group(autoSaveGroup()); applyMainWindowSettings(group); } MainWindow::~MainWindow() { KConfigGroup group = KSharedConfig::openConfig()->group(autoSaveGroup()); saveMainWindowSettings(group); delete m_settings; delete m_baskets; } void MainWindow::setupActions() { actQuit = KStandardAction::quit(this, SLOT(quit()), actionCollection()); QAction *a = NULL; a = actionCollection()->addAction("minimizeRestore", this, SLOT(minimizeRestore())); a->setText(i18n("Minimize")); a->setIcon(QIcon::fromTheme("")); a->setShortcut(0); /** Settings : ************************************************************/ // m_actShowToolbar = KStandardAction::showToolbar( this, SLOT(toggleToolBar()), actionCollection()); m_actShowStatusbar = KStandardAction::showStatusbar(this, SLOT(toggleStatusBar()), actionCollection()); // m_actShowToolbar->setCheckedState( KGuiItem(i18n("Hide &Toolbar")) ); (void)KStandardAction::keyBindings(this, SLOT(showShortcutsSettingsDialog()), actionCollection()); (void)KStandardAction::configureToolbars(this, SLOT(configureToolbars()), actionCollection()); // QAction *actCfgNotifs = KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection() ); // actCfgNotifs->setEnabled(false); // Not yet implemented ! actAppConfig = KStandardAction::preferences(this, SLOT(showSettingsDialog()), actionCollection()); } /*void MainWindow::toggleToolBar() { if (toolBar()->isVisible()) toolBar()->hide(); else toolBar()->show(); saveMainWindowSettings( KSharedConfig::openConfig(), autoSaveGroup() ); }*/ void MainWindow::toggleStatusBar() { if (statusBar()->isVisible()) statusBar()->hide(); else statusBar()->show(); KConfigGroup group = KSharedConfig::openConfig()->group(autoSaveGroup()); saveMainWindowSettings(group); } void MainWindow::configureToolbars() { KConfigGroup group = KSharedConfig::openConfig()->group(autoSaveGroup()); saveMainWindowSettings(group); KEditToolBar dlg(actionCollection()); connect(&dlg, SIGNAL(newToolbarConfig()), this, SLOT(slotNewToolbarConfig())); dlg.exec(); } void MainWindow::configureNotifications() { // TODO // KNotifyDialog *dialog = new KNotifyDialog(this, "KNotifyDialog", false); // dialog->show(); } void MainWindow::slotNewToolbarConfig() // This is called when OK or Apply is clicked { // ...if you use any action list, use plugActionList on each here... createGUI("basketui.rc"); // TODO: Reconnect tags menu aboutToShow() ?? if (!Global::bnpView->isPart()) Global::bnpView->connectTagsMenu(); // The Tags menu was created again! // TODO: Does this do anything? plugActionList(QString::fromLatin1("go_baskets_list"), actBasketsList); KConfigGroup group = KSharedConfig::openConfig()->group(autoSaveGroup()); applyMainWindowSettings(group); } void MainWindow::showSettingsDialog() { if (m_settings == 0) m_settings = new KSettings::Dialog(qApp->activeWindow()); if (Global::activeMainWindow()) { // Help, RestoreDefaults buttons not implemented! m_settings->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel); m_settings->exec(); } else m_settings->show(); } void MainWindow::showShortcutsSettingsDialog() { KShortcutsDialog::configure(actionCollection()); //.setWindowTitle(..) // actionCollection()->writeSettings(); } void MainWindow::ensurePolished() { bool shouldSave = false; // If position and size has never been set, set nice ones: // - Set size to sizeHint() // - Keep the window manager placing the window where it want and save this if (Settings::mainWindowSize().isEmpty()) { // qDebug() << "Main Window Position: Initial Set in show()"; int defaultWidth = qApp->desktop()->width() * 5 / 6; int defaultHeight = qApp->desktop()->height() * 5 / 6; resize(defaultWidth, defaultHeight); // sizeHint() is bad (too small) and we want the user to have a good default area size shouldSave = true; } else { // qDebug() << "Main Window Position: Recall in show(x=" // << Settings::mainWindowPosition().x() << ", y=" << Settings::mainWindowPosition().y() // << ", width=" << Settings::mainWindowSize().width() << ", height=" << Settings::mainWindowSize().height() // << ")"; // move(Settings::mainWindowPosition()); // resize(Settings::mainWindowSize()); } KXmlGuiWindow::ensurePolished(); if (shouldSave) { // qDebug() << "Main Window Position: Save size and position in show(x=" // << pos().x() << ", y=" << pos().y() // << ", width=" << size().width() << ", height=" << size().height() // << ")"; Settings::setMainWindowPosition(pos()); Settings::setMainWindowSize(size()); Settings::saveConfig(); } } void MainWindow::resizeEvent(QResizeEvent *event) { // qDebug() << "Main Window Position: Save size in resizeEvent(width=" << size().width() << ", height=" << size().height() << ") ; isMaximized=" // << (isMaximized() ? "true" : "false"); Settings::setMainWindowSize(size()); Settings::saveConfig(); // Added to make it work (previous lines do not work): // saveMainWindowSettings( KSharedConfig::openConfig(), autoSaveGroup() ); KXmlGuiWindow::resizeEvent(event); } void MainWindow::moveEvent(QMoveEvent *event) { // qDebug() << "Main Window Position: Save position in moveEvent(x=" << pos().x() << ", y=" << pos().y() << ")"; Settings::setMainWindowPosition(pos()); Settings::saveConfig(); // Added to make it work (previous lines do not work): // saveMainWindowSettings( KSharedConfig::openConfig(), autoSaveGroup() ); KXmlGuiWindow::moveEvent(event); } bool MainWindow::queryExit() { hide(); return true; } void MainWindow::quit() { m_quit = true; close(); } bool MainWindow::queryClose() { /* if (m_shuttingDown) // Set in askForQuit(): we don't have to ask again return true;*/ if (qApp->isSavingSession()) { Settings::setStartDocked(false); // If queryClose() is called it's because the window is shown Settings::saveConfig(); return true; } if (Settings::useSystray() && !m_quit /*&& Global::systemTray->parentWidgetTrayClose()*/) { hide(); return false; } else return askForQuit(); } bool MainWindow::askForQuit() { QString message = i18n("

    Do you really want to quit %1?

    ", QGuiApplication::applicationDisplayName()); if (Settings::useSystray()) message += i18n( "

    Notice that you do not have to quit the application before ending your desktop session. " "If you end your session while the application is still running, the application will be reloaded the next time you log in.

    "); int really = KMessageBox::warningContinueCancel(this, message, i18n("Quit Confirm"), KStandardGuiItem::quit(), KStandardGuiItem::cancel(), "confirmQuitAsking"); if (really == KMessageBox::Cancel) { m_quit = false; return false; } return true; } void MainWindow::minimizeRestore() { if (isVisible()) hide(); else show(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 9cc2a73..a89b0d8 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -1,89 +1,76 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef CONTAINER_H #define CONTAINER_H #include class QResizeEvent; class QVBoxLayout; class QMoveEvent; class QWidget; class QAction; class KToggleAction; class BNPView; namespace KSettings { class Dialog; }; /** The window that contain baskets, organized by tabs. * @author Sébastien Laoût */ class MainWindow : public KXmlGuiWindow { Q_OBJECT public: /** Constructor, initializer and destructor */ explicit MainWindow(QWidget *parent = nullptr); ~MainWindow() override; private: void setupActions(); public slots: bool askForQuit(); /** Settings **/ // void toggleToolBar(); void toggleStatusBar(); void showShortcutsSettingsDialog(); void configureToolbars() override; void configureNotifications(); void showSettingsDialog(); void minimizeRestore(); void quit(); void slotNewToolbarConfig(); protected: bool queryExit(); bool queryClose() override; void resizeEvent(QResizeEvent *) override; void moveEvent(QMoveEvent *) override; public: void ensurePolished(); private: // Settings actions : // KToggleAction *m_actShowToolbar; KToggleAction *m_actShowStatusbar; QAction *actQuit; QAction *actAppConfig; QList actBasketsList; private: QVBoxLayout *m_layout; BNPView *m_baskets; bool m_startDocked; KSettings::Dialog *m_settings; bool m_quit; }; #endif // CONTAINER_H diff --git a/src/newbasketdialog.cpp b/src/newbasketdialog.cpp index 8cb8480..dc3f0bc 100644 --- a/src/newbasketdialog.cpp +++ b/src/newbasketdialog.cpp @@ -1,358 +1,345 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "newbasketdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //For Global::mainWindow() #include #include "basketfactory.h" #include "basketlistview.h" #include "basketscene.h" #include "bnpview.h" #include "global.h" #include "kcolorcombo2.h" #include "tools.h" #include "variouswidgets.h" //For HelpLabel /** class SingleSelectionKIconView: */ SingleSelectionKIconView::SingleSelectionKIconView(QWidget *parent) : QListWidget(parent) , m_lastSelected(0) { setViewMode(QListView::IconMode); connect(this, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(slotSelectionChanged(QListWidgetItem *))); } QMimeData *SingleSelectionKIconView::dragObject() { return 0; } void SingleSelectionKIconView::slotSelectionChanged(QListWidgetItem *cur) { if (cur) m_lastSelected = cur; } /** class NewBasketDefaultProperties: */ NewBasketDefaultProperties::NewBasketDefaultProperties() : icon("") , backgroundImage("") , backgroundColor() , textColor() , freeLayout(false) , columnCount(1) { } /** class NewBasketDialog: */ NewBasketDialog::NewBasketDialog(BasketScene *parentBasket, const NewBasketDefaultProperties &defaultProperties, QWidget *parent) : QDialog(parent) , m_defaultProperties(defaultProperties) { // QDialog options setWindowTitle(i18n("New Basket")); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); setObjectName("NewBasket"); setModal(true); QWidget *page = new QWidget(this); QVBoxLayout *topLayout = new QVBoxLayout(page); // Icon, Name and Background Color: QHBoxLayout *nameLayout = new QHBoxLayout; // QHBoxLayout *nameLayout = new QHBoxLayout(this); m_icon = new KIconButton(page); m_icon->setIconType(KIconLoader::NoGroup, KIconLoader::Action); m_icon->setIconSize(16); m_icon->setIcon(m_defaultProperties.icon.isEmpty() ? "basket" : m_defaultProperties.icon); int size = qMax(m_icon->sizeHint().width(), m_icon->sizeHint().height()); m_icon->setFixedSize(size, size); // Make it square! m_icon->setToolTip(i18n("Icon")); m_name = new QLineEdit(/*i18n("Basket"), */ page); m_name->setMinimumWidth(m_name->fontMetrics().maxWidth() * 20); connect(m_name, SIGNAL(textChanged(const QString &)), this, SLOT(nameChanged(const QString &))); m_name->setToolTip(i18n("Name")); m_backgroundColor = new KColorCombo2(QColor(), palette().color(QPalette::Base), page); m_backgroundColor->setColor(QColor()); m_backgroundColor->setFixedSize(m_backgroundColor->sizeHint()); m_backgroundColor->setColor(m_defaultProperties.backgroundColor); m_backgroundColor->setToolTip(i18n("Background color")); nameLayout->addWidget(m_icon); nameLayout->addWidget(m_name); nameLayout->addWidget(m_backgroundColor); topLayout->addLayout(nameLayout); QHBoxLayout *layout = new QHBoxLayout; QPushButton *button = new QPushButton(page); KGuiItem::assign(button, KGuiItem(i18n("&Manage Templates..."), "configure")); connect(button, SIGNAL(clicked()), this, SLOT(manageTemplates())); button->hide(); // Compute the right template to use as the default: QString defaultTemplate = "free"; if (!m_defaultProperties.freeLayout) { if (m_defaultProperties.columnCount == 1) defaultTemplate = "1column"; else if (m_defaultProperties.columnCount == 2) defaultTemplate = "2columns"; else defaultTemplate = "3columns"; } // Empty: // * * * * * // Personal: // *To Do // Professional: // *Meeting Summary // Hobbies: // * m_templates = new SingleSelectionKIconView(page); m_templates->setSelectionMode(QAbstractItemView::SingleSelection); QListWidgetItem *lastTemplate = 0; QPixmap icon(40, 53); QPainter painter(&icon); painter.fillRect(0, 0, icon.width(), icon.height(), palette().color(QPalette::Base)); painter.setPen(palette().color(QPalette::Text)); painter.drawRect(0, 0, icon.width(), icon.height()); painter.end(); lastTemplate = new QListWidgetItem(icon, i18n("One column"), m_templates); if (defaultTemplate == "1column") m_templates->setCurrentItem(lastTemplate); painter.begin(&icon); painter.fillRect(0, 0, icon.width(), icon.height(), palette().color(QPalette::Base)); painter.setPen(palette().color(QPalette::Text)); painter.drawRect(0, 0, icon.width(), icon.height()); painter.drawLine(icon.width() / 2, 0, icon.width() / 2, icon.height()); painter.end(); lastTemplate = new QListWidgetItem(icon, i18n("Two columns"), m_templates); if (defaultTemplate == "2columns") m_templates->setCurrentItem(lastTemplate); painter.begin(&icon); painter.fillRect(0, 0, icon.width(), icon.height(), palette().color(QPalette::Base)); painter.setPen(palette().color(QPalette::Text)); painter.drawRect(0, 0, icon.width(), icon.height()); painter.drawLine(icon.width() / 3, 0, icon.width() / 3, icon.height()); painter.drawLine(icon.width() * 2 / 3, 0, icon.width() * 2 / 3, icon.height()); painter.end(); lastTemplate = new QListWidgetItem(icon, i18n("Three columns"), m_templates); if (defaultTemplate == "3columns") m_templates->setCurrentItem(lastTemplate); painter.begin(&icon); painter.fillRect(0, 0, icon.width(), icon.height(), palette().color(QPalette::Base)); painter.setPen(palette().color(QPalette::Text)); painter.drawRect(0, 0, icon.width(), icon.height()); painter.drawRect(icon.width() / 5, icon.width() / 5, icon.width() / 4, icon.height() / 8); painter.drawRect(icon.width() * 2 / 5, icon.width() * 2 / 5, icon.width() / 4, icon.height() / 8); painter.end(); lastTemplate = new QListWidgetItem(icon, i18n("Free"), m_templates); if (defaultTemplate == "free") m_templates->setCurrentItem(lastTemplate); m_templates->setMinimumHeight(topLayout->minimumSize().width() * 9 / 16); QLabel *label = new QLabel(page); label->setText(i18n("&Template:")); label->setBuddy(m_templates); layout->addWidget(label, /*stretch=*/0, Qt::AlignBottom); layout->addStretch(); layout->addWidget(button, /*stretch=*/0, Qt::AlignBottom); topLayout->addLayout(layout); topLayout->addWidget(m_templates); layout = new QHBoxLayout; m_createIn = new KComboBox(page); m_createIn->addItem(i18n("(Baskets)")); label = new QLabel(page); label->setText(i18n("C&reate in:")); label->setBuddy(m_createIn); HelpLabel *helpLabel = new HelpLabel(i18n("How is it useful?"), i18n("

    Creating baskets inside of other baskets to form a hierarchy allows you to be more organized by eg.:

      " "
    • Grouping baskets by themes or topics;
    • " "
    • Grouping baskets in folders for different projects;
    • " "
    • Making sections with sub-baskets representing chapters or pages;
    • " "
    • Making a group of baskets to export together (to eg. email them to people).
    "), page); layout->addWidget(label); layout->addWidget(m_createIn); layout->addWidget(helpLabel); layout->addStretch(); topLayout->addLayout(layout); m_basketsMap.clear(); int index; m_basketsMap.insert(/*index=*/0, /*basket=*/0L); index = 1; for (int i = 0; i < Global::bnpView->topLevelItemCount(); i++) { index = populateBasketsList(Global::bnpView->topLevelItem(i), /*indent=*/1, /*index=*/index); } connect(m_templates, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(slotOk())); connect(m_templates, SIGNAL(itemActivated(QListWidgetItem *)), this, SLOT(returnPressed())); mainLayout->addWidget(page); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(okButton, SIGNAL(clicked()), SLOT(slotOk())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); okButton->setEnabled(false); if (parentBasket) { int index = 0; for (QMap::Iterator it = m_basketsMap.begin(); it != m_basketsMap.end(); ++it) { if (it.value() == parentBasket) { index = it.key(); break; } } if (index <= 0) return; if (m_createIn->currentIndex() != index) m_createIn->setCurrentIndex(index); } m_name->setFocus(); } void NewBasketDialog::returnPressed() { okButton->animateClick(); } int NewBasketDialog::populateBasketsList(QTreeWidgetItem *item, int indent, int index) { static const int ICON_SIZE = 16; // Get the basket data: BasketScene *basket = ((BasketListViewItem *)item)->basket(); QPixmap icon = KIconLoader::global()->loadIcon(basket->icon(), KIconLoader::NoGroup, ICON_SIZE, KIconLoader::DefaultState, QStringList(), 0L, /*canReturnNull=*/false); icon = Tools::indentPixmap(icon, indent, 2 * ICON_SIZE / 3); m_createIn->addItem(icon, basket->basketName()); m_basketsMap.insert(index, basket); ++index; for (int i = 0; i < item->childCount(); i++) { // Append children of item to the list: index = populateBasketsList(item->child(i), indent + 1, index); } return index; } NewBasketDialog::~NewBasketDialog() { } void NewBasketDialog::ensurePolished() { QDialog::ensurePolished(); m_name->setFocus(); } void NewBasketDialog::nameChanged(const QString &newName) { okButton->setEnabled(!newName.isEmpty()); } void NewBasketDialog::slotOk() { QListWidgetItem *item = ((SingleSelectionKIconView *)m_templates)->selectedItem(); QString templateName; if (!item) return; if (item->text() == i18n("One column")) templateName = "1column"; if (item->text() == i18n("Two columns")) templateName = "2columns"; if (item->text() == i18n("Three columns")) templateName = "3columns"; if (item->text() == i18n("Free-form")) templateName = "free"; if (item->text() == i18n("Mind map")) templateName = "mindmap"; Global::bnpView->closeAllEditors(); QString backgroundImage; QColor textColor; if (m_backgroundColor->color() == m_defaultProperties.backgroundColor) { backgroundImage = m_defaultProperties.backgroundImage; textColor = m_defaultProperties.textColor; } BasketFactory::newBasket(m_icon->icon(), m_name->text(), backgroundImage, m_backgroundColor->color(), textColor, templateName, m_basketsMap[m_createIn->currentIndex()]); if (Global::activeMainWindow()) Global::activeMainWindow()->show(); } void NewBasketDialog::manageTemplates() { KMessageBox::information(this, "Wait a minute! There is no template for now: they will come with time... :-D"); } diff --git a/src/newbasketdialog.h b/src/newbasketdialog.h index 3e1ca51..6dc9fc0 100644 --- a/src/newbasketdialog.h +++ b/src/newbasketdialog.h @@ -1,105 +1,92 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef NEWBASKETDIALOG_H #define NEWBASKETDIALOG_H #include #include #include class KIconButton; class QLineEdit; class QMimeData; class KComboBox; class QTreeWidgetItem; class BasketScene; class KColorCombo2; /** The class QListWidget allow to drag items. We don't want to, so we disable it. * This class also unselect the selected item when the user right click an empty space. We don't want to, so we reselect it if that happens. * @author Sébastien Laoût */ class SingleSelectionKIconView : public QListWidget { Q_OBJECT public: explicit SingleSelectionKIconView(QWidget *parent = nullptr); QMimeData *dragObject(); QListWidgetItem *selectedItem() { return m_lastSelected; } private slots: void slotSelectionChanged(QListWidgetItem *cur); private: QListWidgetItem *m_lastSelected; }; /** Struct to store default properties of a new basket. * When the dialog shows up, the @p icon is used, as well as the @p backgroundColor. * A template is chosen depending on @p freeLayout and @p columnLayout. * If @p columnLayout is too high, the template with the more columns will be chosen instead. * If the user change the background color in the dialog, then @p backgroundImage and @p textColor will not be used! * @author Sébastien Laoût */ struct NewBasketDefaultProperties { QString icon; QString backgroundImage; QColor backgroundColor; QColor textColor; bool freeLayout; int columnCount; NewBasketDefaultProperties(); }; /** The dialog to create a new basket from a template. * @author Sébastien Laoût */ class NewBasketDialog : public QDialog { Q_OBJECT public: NewBasketDialog(BasketScene *parentBasket, const NewBasketDefaultProperties &defaultProperties, QWidget *parent = 0); ~NewBasketDialog() override; void ensurePolished(); protected slots: void slotOk(); void returnPressed(); void manageTemplates(); void nameChanged(const QString &newName); private: int populateBasketsList(QTreeWidgetItem *item, int indent, int index); NewBasketDefaultProperties m_defaultProperties; KIconButton *m_icon; QLineEdit *m_name; KColorCombo2 *m_backgroundColor; QListWidget *m_templates; KComboBox *m_createIn; QMap m_basketsMap; QPushButton *okButton; }; #endif // NEWBASKETDIALOG_H diff --git a/src/note.cpp b/src/note.cpp index 4611996..0fc8284 100644 --- a/src/note.cpp +++ b/src/note.cpp @@ -1,2605 +1,2592 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "note.h" #include #include #include #include //For KGLobal::locale( #include #include #include #include #include #include #include #include // sqrt() and pow() functions #include // rand() function #include "basketscene.h" #include "debugwindow.h" #include "filter.h" #include "notefactory.h" // For NoteFactory::filteredURL() #include "noteselection.h" #include "settings.h" #include "tag.h" #include "tools.h" /** class Note: */ #define FOR_EACH_CHILD(childVar) for (Note *childVar = firstChild(); childVar; childVar = childVar->next()) class NotePrivate { public: NotePrivate() : prev(0) , next(0) , width(-1) , height(Note::MIN_HEIGHT) { } Note *prev; Note *next; qreal width; qreal height; }; qreal Note::NOTE_MARGIN = 2; qreal Note::INSERTION_HEIGHT = 5; qreal Note::EXPANDER_WIDTH = 9; qreal Note::EXPANDER_HEIGHT = 9; qreal Note::GROUP_WIDTH = 2 * NOTE_MARGIN + EXPANDER_WIDTH; qreal Note::HANDLE_WIDTH = GROUP_WIDTH; qreal Note::RESIZER_WIDTH = GROUP_WIDTH; qreal Note::TAG_ARROW_WIDTH = 5; qreal Note::EMBLEM_SIZE = 16; qreal Note::MIN_HEIGHT = 2 * NOTE_MARGIN + EMBLEM_SIZE; Note::Note(BasketScene *parent) : d(new NotePrivate) , m_groupWidth(250) , m_isFolded(false) , m_firstChild(0L) , m_parentNote(0) , m_basket(parent) , m_content(0) , m_addedDate(QDateTime::currentDateTime()) , m_lastModificationDate(QDateTime::currentDateTime()) , m_computedAreas(false) , m_onTop(false) , m_animation(0) , m_hovered(false) , m_hoveredZone(Note::None) , m_focused(false) , m_selected(false) , m_wasInLastSelectionRect(false) , m_computedState() , m_emblemsCount(0) , m_haveInvisibleTags(false) , m_matching(true) { setHeight(MIN_HEIGHT); if (m_basket) { m_basket->addItem(this); } } Note::~Note() { if (m_basket) { if (m_content && m_content->graphicsItem()) { m_basket->removeItem(m_content->graphicsItem()); } m_basket->removeItem(this); } delete m_content; delete m_animation; deleteChilds(); } void Note::setNext(Note *next) { d->next = next; } Note *Note::next() const { return d->next; } void Note::setPrev(Note *prev) { d->prev = prev; } Note *Note::prev() const { return d->prev; } qreal Note::bottom() const { return y() + height() - 1; } void Note::setParentBasket(BasketScene *basket) { if (m_basket) m_basket->removeItem(this); m_basket = basket; if (m_basket) m_basket->addItem(this); } QString Note::addedStringDate() { return m_addedDate.toString(); } QString Note::lastModificationStringDate() { return m_lastModificationDate.toString(); } QString Note::toText(const QString &cuttedFullPath) { if (content()) { // Convert note to text: QString text = content()->toText(cuttedFullPath); // If we should not export tags with the text, return immediately: if (!Settings::exportTextTags()) return text; // Compute the text equivalent of the tag states: QString firstLine; QString otherLines; for (State::List::Iterator it = m_states.begin(); it != m_states.end(); ++it) { if (!(*it)->textEquivalent().isEmpty()) { firstLine += (*it)->textEquivalent() + ' '; if ((*it)->onAllTextLines()) otherLines += (*it)->textEquivalent() + ' '; } } // Merge the texts: if (firstLine.isEmpty()) return text; if (otherLines.isEmpty()) return firstLine + text; QStringList lines = text.split('\n'); QString result = firstLine + lines[0] + (lines.count() > 1 ? "\n" : ""); for (int i = 1 /*Skip the first line*/; i < lines.count(); ++i) result += otherLines + lines[i] + (i < lines.count() - 1 ? "\n" : ""); return result; } else return ""; } bool Note::computeMatching(const FilterData &data) { // Groups are always matching: if (!content()) return true; // If we were editing this note and there is a save operation in the middle, then do not hide it suddenly: if (basket()->editedNote() == this) return true; bool matching; // First match tags (they are fast to compute): switch (data.tagFilterType) { default: case FilterData::DontCareTagsFilter: matching = true; break; case FilterData::NotTaggedFilter: matching = m_states.count() <= 0; break; case FilterData::TaggedFilter: matching = m_states.count() > 0; break; case FilterData::TagFilter: matching = hasTag(data.tag); break; case FilterData::StateFilter: matching = hasState(data.state); break; } // Don't try to match the content text if we are not matching now (the filter is of 'AND' type) or if we shouldn't try to match the string: if (matching && !data.string.isEmpty()) matching = content()->match(data); return matching; } int Note::newFilter(const FilterData &data) { bool wasMatching = matching(); m_matching = computeMatching(data); setOnTop(wasMatching && matching()); if (!matching()) { setSelected(false); hide(); } else if (!wasMatching) { show(); } int countMatches = (content() && matching() ? 1 : 0); FOR_EACH_CHILD(child) { countMatches += child->newFilter(data); } return countMatches; } void Note::deleteSelectedNotes(bool deleteFilesToo, QSet *notesToBeDeleted) { if (content()) { if (isSelected()) { basket()->unplugNote(this); if (deleteFilesToo && content()->useFile()) { Tools::deleteRecursively(fullPath()); // basket()->deleteFiles(fullPath()); // Also delete the folder if it's a folder } if (notesToBeDeleted) { notesToBeDeleted->insert(this); } } return; } bool isColumn = this->isColumn(); Note *child = firstChild(); Note *next; while (child) { next = child->next(); // If we delete 'child' on the next line, child->next() will be 0! child->deleteSelectedNotes(deleteFilesToo, notesToBeDeleted); child = next; } // if it remains at least two notes, the group must not be deleted if (!isColumn && !(firstChild() && firstChild()->next())) { if (notesToBeDeleted) { notesToBeDeleted->insert(this); } } } int Note::count() { if (content()) return 1; int count = 0; FOR_EACH_CHILD(child) { count += child->count(); } return count; } int Note::countDirectChilds() { int count = 0; FOR_EACH_CHILD(child) { ++count; } return count; } QString Note::fullPath() { if (content()) return basket()->fullPath() + content()->fileName(); else return ""; } /*void Note::update() { update(0,0,boundingRect().width,boundingRect().height); }*/ void Note::setFocused(bool focused) { if (m_focused == focused) return; m_focused = focused; unbufferize(); update(); // FIXME: ??? } void Note::setSelected(bool selected) { if (isGroup()) selected = false; // A group cannot be selected! if (m_selected == selected) return; if (!selected && basket()->editedNote() == this) { // basket()->closeEditor(); return; // To avoid a bug that would count 2 less selected notes instead of 1 less! Because m_selected is modified only below. } if (selected) basket()->addSelectedNote(); else basket()->removeSelectedNote(); m_selected = selected; unbufferize(); update(); // FIXME: ??? } void Note::resetWasInLastSelectionRect() { m_wasInLastSelectionRect = false; FOR_EACH_CHILD(child) { child->resetWasInLastSelectionRect(); } } void Note::finishLazyLoad() { if (content()) content()->finishLazyLoad(); FOR_EACH_CHILD(child) { child->finishLazyLoad(); } } void Note::selectIn(const QRectF &rect, bool invertSelection, bool unselectOthers /*= true*/) { // QRect myRect(x(), y(), width(), height()); // bool intersects = myRect.intersects(rect); // Only intersects with visible areas. // If the note is not visible, the user don't think it will be selected while selecting the note(s) that hide this, so act like the user think: bool intersects = false; for (QList::iterator it = m_areas.begin(); it != m_areas.end(); ++it) { QRectF &r = *it; if (r.intersects(rect)) { intersects = true; break; } } bool toSelect = intersects || (!unselectOthers && isSelected()); if (invertSelection) { toSelect = (m_wasInLastSelectionRect == intersects) ? isSelected() : !isSelected(); } setSelected(toSelect); m_wasInLastSelectionRect = intersects; Note *child = firstChild(); bool first = true; while (child) { if ((showSubNotes() || first) && child->matching()) child->selectIn(rect, invertSelection, unselectOthers); else child->setSelectedRecursively(false); child = child->next(); first = false; } } bool Note::allSelected() { if (isGroup()) { Note *child = firstChild(); bool first = true; while (child) { if ((showSubNotes() || first) && child->matching()) if (!child->allSelected()) return false; ; child = child->next(); first = false; } return true; } else return isSelected(); } void Note::setSelectedRecursively(bool selected) { setSelected(selected && matching()); FOR_EACH_CHILD(child) { child->setSelectedRecursively(selected); } } void Note::invertSelectionRecursively() { if (content()) setSelected(!isSelected() && matching()); FOR_EACH_CHILD(child) { child->invertSelectionRecursively(); } } void Note::unselectAllBut(Note *toSelect) { if (this == toSelect) setSelectedRecursively(true); else { setSelected(false); Note *child = firstChild(); bool first = true; while (child) { if ((showSubNotes() || first) && child->matching()) child->unselectAllBut(toSelect); else child->setSelectedRecursively(false); child = child->next(); first = false; } } } void Note::invertSelectionOf(Note *toSelect) { if (this == toSelect) setSelectedRecursively(!isSelected()); else { Note *child = firstChild(); bool first = true; while (child) { if ((showSubNotes() || first) && child->matching()) child->invertSelectionOf(toSelect); child = child->next(); first = false; } } } Note *Note::theSelectedNote() { if (!isGroup() && isSelected()) return this; Note *selectedOne; Note *child = firstChild(); while (child) { selectedOne = child->theSelectedNote(); if (selectedOne) return selectedOne; child = child->next(); } return 0; } NoteSelection *Note::selectedNotes() { if (content()) { if (isSelected()) return new NoteSelection(this); else return 0; } NoteSelection *selection = new NoteSelection(this); FOR_EACH_CHILD(child) { selection->append(child->selectedNotes()); } if (selection->firstChild) { if (selection->firstChild->next) return selection; else { // If 'selection' is a group with only one content, return directly that content: NoteSelection *reducedSelection = selection->firstChild; // delete selection; // TODO: Cut all connections of 'selection' before deleting it! for (NoteSelection *node = reducedSelection; node; node = node->next) node->parent = 0; return reducedSelection; } } else { delete selection; return 0; } } bool Note::isAfter(Note *note) { if (this == 0 || note == 0) return true; Note *next = this; while (next) { if (next == note) return false; next = next->nextInStack(); } return true; } bool Note::containsNote(Note *note) { // if (this == note) // return true; while (note) if (note == this) return true; else note = note->parentNote(); // FOR_EACH_CHILD (child) // if (child->containsNote(note)) // return true; return false; } Note *Note::firstRealChild() { Note *child = m_firstChild; while (child) { if (!child->isGroup() /*&& child->matching()*/) return child; child = child->firstChild(); } // Empty group: return 0; } Note *Note::lastRealChild() { Note *child = lastChild(); while (child) { if (child->content()) return child; Note *possibleChild = child->lastRealChild(); if (possibleChild && possibleChild->content()) return possibleChild; child = child->prev(); } return 0; } Note *Note::lastChild() { Note *child = m_firstChild; while (child && child->next()) child = child->next(); return child; } Note *Note::lastSibling() { Note *last = this; while (last && last->next()) last = last->next(); return last; } qreal Note::yExpander() { Note *child = firstRealChild(); if (child && !child->isShown()) child = child->nextShownInStack(); // FIXME: Restrict scope to 'this' if (child) return (child->boundingRect().height() - EXPANDER_HEIGHT) / 2; else // Groups always have at least 2 notes, except for columns which can have no child (but should exists anyway): return 0; } bool Note::isFree() const { return parentNote() == 0 && basket() && basket()->isFreeLayout(); } bool Note::isColumn() const { return parentNote() == 0 && basket() && basket()->isColumnsLayout(); } bool Note::hasResizer() const { // "isFree" || "isColumn but not the last" return parentNote() == 0 && ((basket() && basket()->isFreeLayout()) || d->next != 0L); } qreal Note::resizerHeight() const { return (isColumn() ? basket()->sceneRect().height() : d->height); } void Note::setHoveredZone(Zone zone) // TODO: Remove setHovered(bool) and assume it is hovered if zone != None !!!!!!! { if (m_hoveredZone != zone) { if (content()) content()->setHoveredZone(m_hoveredZone, zone); m_hoveredZone = zone; unbufferize(); } } Note::Zone Note::zoneAt(const QPointF &pos, bool toAdd) { // Keep the resizer highlighted when resizong, even if the cursor is over another note: if (basket()->resizingNote() == this) return Resizer; // When dropping/pasting something on a column resizer, add it at the bottom of the column, and don't group it with the whole column: if (toAdd && isColumn() && hasResizer()) { qreal right = rightLimit() - x(); if ((pos.x() >= right) && (pos.x() < right + RESIZER_WIDTH) && (pos.y() >= 0) && (pos.y() < resizerHeight())) // Code copied from below return BottomColumn; } // Below a column: if (isColumn()) { if (pos.y() >= height() && pos.x() < rightLimit() - x()) return BottomColumn; } // If toAdd, return only TopInsert, TopGroup, BottomInsert or BottomGroup // (by spanning those areas in 4 equal rectangles in the note): if (toAdd) { if (!isFree() && !Settings::groupOnInsertionLine()) { if (pos.y() < height() / 2) return TopInsert; else return BottomInsert; } if (isColumn() && pos.y() >= height()) return BottomGroup; if (pos.y() < height() / 2) if (pos.x() < width() / 2 && !isFree()) return TopInsert; else return TopGroup; else if (pos.x() < width() / 2 && !isFree()) return BottomInsert; else return BottomGroup; } // If in the resizer: if (hasResizer()) { qreal right = rightLimit() - x(); if ((pos.x() >= right) && (pos.x() < right + RESIZER_WIDTH) && (pos.y() >= 0) && (pos.y() < resizerHeight())) return Resizer; } // If isGroup, return only Group, GroupExpander, TopInsert or BottomInsert: if (isGroup()) { if (pos.y() < INSERTION_HEIGHT) { if (isFree()) return TopGroup; else return TopInsert; } if (pos.y() >= height() - INSERTION_HEIGHT) { if (isFree()) return BottomGroup; else return BottomInsert; } if (pos.x() >= NOTE_MARGIN && pos.x() < NOTE_MARGIN + EXPANDER_WIDTH) { qreal yExp = yExpander(); if (pos.y() >= yExp && pos.y() < yExp + EXPANDER_HEIGHT) return GroupExpander; } if (pos.x() < width()) return Group; else return Note::None; } // Else, it's a normal note: if (pos.x() < HANDLE_WIDTH) return Handle; if (pos.y() < INSERTION_HEIGHT) { if ((!isFree() && !Settings::groupOnInsertionLine()) || (pos.x() < width() / 2 && !isFree())) return TopInsert; else return TopGroup; } if (pos.y() >= height() - INSERTION_HEIGHT) { if ((!isFree() && !Settings::groupOnInsertionLine()) || (pos.x() < width() / 2 && !isFree())) return BottomInsert; else return BottomGroup; } for (int i = 0; i < m_emblemsCount; i++) { if (pos.x() >= HANDLE_WIDTH + (NOTE_MARGIN + EMBLEM_SIZE) * i && pos.x() < HANDLE_WIDTH + (NOTE_MARGIN + EMBLEM_SIZE) * i + NOTE_MARGIN + EMBLEM_SIZE) return (Zone)(Emblem0 + i); } if (pos.x() < HANDLE_WIDTH + (NOTE_MARGIN + EMBLEM_SIZE) * m_emblemsCount + NOTE_MARGIN + TAG_ARROW_WIDTH + NOTE_MARGIN) return TagsArrow; if (!linkAt(pos).isEmpty()) return Link; int customZone = content()->zoneAt(pos - QPointF(contentX(), NOTE_MARGIN)); if (customZone) return (Note::Zone)customZone; return Content; } QString Note::linkAt(const QPointF &pos) { QString link = m_content->linkAt(pos - QPointF(contentX(), NOTE_MARGIN)); if (link.isEmpty() || link.startsWith(QLatin1String("basket://"))) return link; else return NoteFactory::filteredURL(QUrl::fromUserInput(link)).toDisplayString(); // KURIFilter::self()->filteredURI(link); } qreal Note::contentX() const { return HANDLE_WIDTH + NOTE_MARGIN + (EMBLEM_SIZE + NOTE_MARGIN) * m_emblemsCount + TAG_ARROW_WIDTH + NOTE_MARGIN; } QRectF Note::zoneRect(Note::Zone zone, const QPointF &pos) { if (zone >= Emblem0) return QRect(HANDLE_WIDTH + (NOTE_MARGIN + EMBLEM_SIZE) * (zone - Emblem0), INSERTION_HEIGHT, NOTE_MARGIN + EMBLEM_SIZE, height() - 2 * INSERTION_HEIGHT); qreal yExp; qreal right; qreal xGroup = (isFree() ? (isGroup() ? 0 : GROUP_WIDTH) : width() / 2); QRectF rect; qreal insertSplit = (Settings::groupOnInsertionLine() ? 2 : 1); switch (zone) { case Note::Handle: return QRectF(0, 0, HANDLE_WIDTH, d->height); case Note::Group: yExp = yExpander(); if (pos.y() < yExp) { return QRectF(0, INSERTION_HEIGHT, d->width, yExp - INSERTION_HEIGHT); } if (pos.y() > yExp + EXPANDER_HEIGHT) { return QRectF(0, yExp + EXPANDER_HEIGHT, d->width, d->height - yExp - EXPANDER_HEIGHT - INSERTION_HEIGHT); } if (pos.x() < NOTE_MARGIN) { return QRectF(0, 0, NOTE_MARGIN, d->height); } else { return QRectF(d->width - NOTE_MARGIN, 0, NOTE_MARGIN, d->height); } case Note::TagsArrow: return QRectF(HANDLE_WIDTH + (NOTE_MARGIN + EMBLEM_SIZE) * m_emblemsCount, INSERTION_HEIGHT, NOTE_MARGIN + TAG_ARROW_WIDTH + NOTE_MARGIN, d->height - 2 * INSERTION_HEIGHT); case Note::Custom0: case Note::Content: rect = content()->zoneRect(zone, pos - QPointF(contentX(), NOTE_MARGIN)); rect.translate(contentX(), NOTE_MARGIN); return rect.intersected(QRectF(contentX(), INSERTION_HEIGHT, d->width - contentX(), d->height - 2 * INSERTION_HEIGHT)); // Only IN contentRect case Note::GroupExpander: return QRectF(NOTE_MARGIN, yExpander(), EXPANDER_WIDTH, EXPANDER_HEIGHT); case Note::Resizer: right = rightLimit(); return QRectF(right - x(), 0, RESIZER_WIDTH, resizerHeight()); case Note::Link: case Note::TopInsert: if (isGroup()) return QRectF(0, 0, d->width, INSERTION_HEIGHT); else return QRectF(HANDLE_WIDTH, 0, d->width / insertSplit - HANDLE_WIDTH, INSERTION_HEIGHT); case Note::TopGroup: return QRectF(xGroup, 0, d->width - xGroup, INSERTION_HEIGHT); case Note::BottomInsert: if (isGroup()) return QRectF(0, d->height - INSERTION_HEIGHT, d->width, INSERTION_HEIGHT); else return QRectF(HANDLE_WIDTH, d->height - INSERTION_HEIGHT, d->width / insertSplit - HANDLE_WIDTH, INSERTION_HEIGHT); case Note::BottomGroup: return QRectF(xGroup, d->height - INSERTION_HEIGHT, d->width - xGroup, INSERTION_HEIGHT); case Note::BottomColumn: return QRectF(0, d->height, rightLimit() - x(), basket()->sceneRect().height() - d->height); case Note::None: return QRectF(/*0, 0, -1, -1*/); default: return QRectF(/*0, 0, -1, -1*/); } } Qt::CursorShape Note::cursorFromZone(Zone zone) const { switch (zone) { case Note::Handle: case Note::Group: return Qt::SizeAllCursor; break; case Note::Resizer: if (isColumn()) { return Qt::SplitHCursor; } else { return Qt::SizeHorCursor; } break; case Note::Custom0: return m_content->cursorFromZone(zone); break; case Note::Link: case Note::TagsArrow: case Note::GroupExpander: return Qt::PointingHandCursor; break; case Note::Content: return Qt::IBeamCursor; break; case Note::TopInsert: case Note::TopGroup: case Note::BottomInsert: case Note::BottomGroup: case Note::BottomColumn: return Qt::CrossCursor; break; case Note::None: return Qt::ArrowCursor; break; default: State *state = stateForEmblemNumber(zone - Emblem0); if (state && state->parentTag()->states().count() > 1) return Qt::PointingHandCursor; else return Qt::ArrowCursor; } } bool Note::initAnimationLoad(QTimeLine *timeLine) { bool needAnimation = false; if (!isColumn()) { qreal x, y; switch (rand() % 4) { case 0: // Put it on top: x = basket()->sceneRect().x() + rand() % (int)basket()->sceneRect().width(); y = -height(); break; case 1: // Put it on bottom: x = basket()->sceneRect().x() + rand() % (int)basket()->sceneRect().width(); y = basket()->sceneRect().y() + basket()->graphicsView()->viewport()->height(); break; case 2: // Put it on left: x = -width() - (hasResizer() ? Note::RESIZER_WIDTH : 0); y = basket()->sceneRect().y() + rand() % basket()->graphicsView()->viewport()->height(); break; case 3: // Put it on right: default: // In the case of... x = basket()->sceneRect().x() + basket()->graphicsView()->viewport()->width(); y = basket()->sceneRect().y() + rand() % basket()->graphicsView()->viewport()->height(); break; } m_animation = new QGraphicsItemAnimation; m_animation->setItem(this); m_animation->setTimeLine(timeLine); for (int i = 0; i <= 100; i++) { m_animation->setPosAt(i / 100.0, QPointF(this->x() - x * (100 - i) / 100, this->y() - y * (100 - i) / 100)); } needAnimation = true; } if (isGroup()) { const qreal viewHeight = basket()->sceneRect().y() + basket()->graphicsView()->viewport()->height(); Note *child = firstChild(); bool first = true; while (child) { if (child->y() < viewHeight) { if ((showSubNotes() || first) && child->matching()) needAnimation |= child->initAnimationLoad(timeLine); } else break; // 'child' are not a free notes (because child of at least one note, 'this'), so 'child' is ordered vertically. child = child->next(); first = false; } } return needAnimation; } void Note::animationFinished() { unbufferize(); delete m_animation; m_animation = 0; Note *child = firstChild(); while (child) { child->animationFinished(); child = child->next(); } } qreal Note::height() const { return d->height; } void Note::setHeight(qreal height) { setInitialHeight(height); } void Note::setInitialHeight(qreal height) { prepareGeometryChange(); d->height = height; } void Note::unsetWidth() { prepareGeometryChange(); d->width = 0; unbufferize(); FOR_EACH_CHILD(child) child->unsetWidth(); } qreal Note::width() const { return (isGroup() ? (isColumn() ? 0 : GROUP_WIDTH) : d->width); } void Note::requestRelayout() { prepareGeometryChange(); d->width = 0; unbufferize(); basket()->relayoutNotes(true); // TODO: A signal that will relayout ONCE and DELAYED if called several times } void Note::setWidth(qreal width) // TODO: inline ? { if (d->width != width) setWidthForceRelayout(width); } void Note::setWidthForceRelayout(qreal width) { prepareGeometryChange(); unbufferize(); d->width = (width < minWidth() ? minWidth() : width); int contentWidth = width - contentX() - NOTE_MARGIN; if (m_content) { ///// FIXME: is this OK? if (contentWidth < 1) contentWidth = 1; if (contentWidth < m_content->minWidth()) contentWidth = m_content->minWidth(); setHeight(m_content->setWidthAndGetHeight(contentWidth /* < 1 ? 1 : contentWidth*/) + 2 * NOTE_MARGIN); if (d->height < 3 * INSERTION_HEIGHT) // Assure a minimal size... setHeight(3 * INSERTION_HEIGHT); } } qreal Note::minWidth() const { if (m_content) return contentX() + m_content->minWidth() + NOTE_MARGIN; else return GROUP_WIDTH; ///// FIXME: is this OK? } qreal Note::minRight() { if (isGroup()) { qreal right = x() + width(); Note *child = firstChild(); bool first = true; while (child) { if ((showSubNotes() || first) && child->matching()) right = qMax(right, child->minRight()); child = child->next(); first = false; } if (isColumn()) { qreal minColumnRight = x() + 2 * HANDLE_WIDTH; if (right < minColumnRight) return minColumnRight; } return right; } else return x() + minWidth(); } bool Note::toggleFolded() { // Close the editor if it was editing a note that we are about to hide after collapsing: if (!m_isFolded && basket() && basket()->isDuringEdit()) { if (containsNote(basket()->editedNote()) && firstRealChild() != basket()->editedNote()) basket()->closeEditor(); } // Important to close the editor FIRST, because else, the last edited note would not show during folding animation (don't ask me why ;-) ): m_isFolded = !m_isFolded; unbufferize(); return true; } Note *Note::noteAt(QPointF pos) { if (matching() && hasResizer()) { int right = rightLimit(); // TODO: This code is duplicated 3 times: !!!! if ((pos.x() >= right) && (pos.x() < right + RESIZER_WIDTH) && (pos.y() >= y()) && (pos.y() < y() + resizerHeight())) { if (!m_computedAreas) recomputeAreas(); for (QList::iterator it = m_areas.begin(); it != m_areas.end(); ++it) { QRectF &rect = *it; if (rect.contains(pos.x(), pos.y())) return this; } } } if (isGroup()) { if ((pos.x() >= x()) && (pos.x() < x() + width()) && (pos.y() >= y()) && (pos.y() < y() + d->height)) { if (!m_computedAreas) recomputeAreas(); for (QList::iterator it = m_areas.begin(); it != m_areas.end(); ++it) { QRectF &rect = *it; if (rect.contains(pos.x(), pos.y())) return this; } return NULL; } Note *child = firstChild(); Note *found; bool first = true; while (child) { if ((showSubNotes() || first) && child->matching()) { found = child->noteAt(pos); if (found) return found; } child = child->next(); first = false; } } else if (matching() && pos.y() >= y() && pos.y() < y() + d->height && pos.x() >= x() && pos.x() < x() + d->width) { if (!m_computedAreas) recomputeAreas(); for (QList::iterator it = m_areas.begin(); it != m_areas.end(); ++it) { QRectF &rect = *it; if (rect.contains(pos.x(), pos.y())) return this; } return NULL; } return NULL; } QRectF Note::boundingRect() const { if (hasResizer()) { return QRectF(0, 0, rightLimit() - x() + RESIZER_WIDTH, resizerHeight()); } return QRectF(0, 0, width(), height()); } QRectF Note::resizerRect() { return QRectF(rightLimit(), y(), RESIZER_WIDTH, resizerHeight()); } bool Note::showSubNotes() { return !m_isFolded || basket()->isFiltering(); } void Note::relayoutAt(qreal ax, qreal ay, bool animate) { if (!matching()) return; m_computedAreas = false; m_areas.clear(); // Don't relayout free notes one under the other, because by definition they are freely positionned! if (isFree()) { ax = x(); ay = y(); // If it's a column, it always have the same "fixed" position (no animation): } else if (isColumn()) { ax = (prev() ? prev()->rightLimit() + RESIZER_WIDTH : 0); ay = 0; setX(ax); setY(ay); // But relayout others vertically if they are inside such primary groups or if it is a "normal" basket: } else { setX(ax); setY(ay); } // Then, relayout sub-notes (only the first, if the group is folded) and so, assign an height to the group: if (isGroup()) { qreal h = 0; Note *child = firstChild(); bool first = true; while (child) { if (child->matching() && (!m_isFolded || first || basket()->isFiltering())) { // Don't use showSubNotes() but use !m_isFolded because we don't want a relayout for the animated collapsing notes child->relayoutAt(ax + width(), ay + h, animate); h += child->height(); if (!child->isVisible()) child->show(); } else { // In case the user collapse a group, then move it and then expand it: child->setXRecursively(x() + width()); // notes SHOULD have a good X coordinate, and not the old one! if (child->isVisible()) child->hideRecursively(); } // For future animation when re-match, but on bottom of already matched notes! // Find parent primary note and set the Y to THAT y: // if (!child->matching()) // child->setY(parentPrimaryNote()->y()); child = child->next(); first = false; } if (height() != h || d->height != h) { unbufferize(); /*if (animate) addAnimation(0, 0, h - height()); else {*/ setHeight(h); unbufferize(); //} } } else { // If rightLimit is exceeded, set the top-level right limit!!! // and NEED RELAYOUT setWidth(finalRightLimit() - x()); } // Set the basket area limits (but not for child notes: no need, because they will look for their parent note): if (!parentNote()) { if (basket()->tmpWidth < finalRightLimit() + (hasResizer() ? RESIZER_WIDTH : 0)) basket()->tmpWidth = finalRightLimit() + (hasResizer() ? RESIZER_WIDTH : 0); if (basket()->tmpHeight < y() + height()) basket()->tmpHeight = y() + height(); // However, if the note exceed the allowed size, let it! : } else if (!isGroup()) { if (basket()->tmpWidth < x() + width() + (hasResizer() ? RESIZER_WIDTH : 0)) basket()->tmpWidth = x() + width() + (hasResizer() ? RESIZER_WIDTH : 0); if (basket()->tmpHeight < y() + height()) basket()->tmpHeight = y() + height(); } } void Note::setXRecursively(qreal x) { setX(x); FOR_EACH_CHILD(child) child->setXRecursively(x + width()); } void Note::setYRecursively(qreal y) { setY(y); FOR_EACH_CHILD(child) child->setYRecursively(y); } void Note::hideRecursively() { hide(); FOR_EACH_CHILD(child) child->hideRecursively(); } void Note::setGroupWidth(qreal width) { m_groupWidth = width; } qreal Note::groupWidth() const { if (hasResizer()) return m_groupWidth; else return rightLimit() - x(); } qreal Note::rightLimit() const { if (isColumn() && d->next == 0L) // The last column return qMax((x() + minWidth()), (qreal)basket()->graphicsView()->viewport()->width()); else if (parentNote()) return parentNote()->rightLimit(); else return x() + m_groupWidth; } qreal Note::finalRightLimit() const { if (isColumn() && d->next == 0L) // The last column return qMax(x() + minWidth(), (qreal)basket()->graphicsView()->viewport()->width()); else if (parentNote()) return parentNote()->finalRightLimit(); else return x() + m_groupWidth; } void Note::drawExpander(QPainter *painter, qreal x, qreal y, const QColor &background, bool expand, BasketScene *basket) { QStyleOption opt; opt.state = (expand ? QStyle::State_On : QStyle::State_Off); opt.rect = QRect(x, y, 9, 9); opt.palette = basket->palette(); opt.palette.setColor(QPalette::Base, background); painter->fillRect(opt.rect, background); QStyle *style = basket->style(); if (!expand) { style->drawPrimitive(QStyle::PE_IndicatorArrowDown, &opt, painter, basket->graphicsView()->viewport()); } else { style->drawPrimitive(QStyle::PE_IndicatorArrowRight, &opt, painter, basket->graphicsView()->viewport()); } } QColor expanderBackground(qreal height, qreal y, const QColor &foreground) { // We will divide height per two, subtract one and use that below a division bar: // To avoid division by zero error, height should be bigger than 3. // And to avoid y errors or if y is on the borders, we return the border color: the background color. if (height <= 3 || y <= 0 || y >= height - 1) return foreground; QColor dark = foreground.dark(110); // 1/1.1 of brightness QColor light = foreground.light(150); // 50% brighter qreal h1, h2, s1, s2, v1, v2; int ng; if (y <= (height - 2) / 2) { light.getHsvF(&h1, &s1, &v1); dark.getHsvF(&h2, &s2, &v2); ng = (height - 2) / 2; y -= 1; } else { dark.getHsvF(&h1, &s1, &v1); foreground.getHsvF(&h2, &s2, &v2); ng = (height - 2) - (height - 2) / 2; y -= 1 + (height - 2) / 2; } return QColor::fromHsvF(h1 + ((h2 - h1) * y) / (ng - 1), s1 + ((s2 - s1) * y) / (ng - 1), v1 + ((v2 - v1) * y) / (ng - 1)); } void Note::drawHandle(QPainter *painter, qreal x, qreal y, qreal width, qreal height, const QColor &background, const QColor &foreground, const QColor &lightForeground) { const QPen backgroundPen(background); const QPen foregroundPen(foreground); // Draw the surrounding rectangle: painter->setPen(foregroundPen); painter->drawLine(0, 0, width - 1, 0); painter->drawLine(0, 0, 0, height - 1); painter->drawLine(0, height - 1, width - 1, height - 1); // Draw the gradients: painter->fillRect(1 + x, 1 + y, width - 2, height - 2, lightForeground); // Round the top corner with background color: painter->setPen(backgroundPen); painter->drawLine(0, 0, 0, 3); painter->drawLine(1, 0, 3, 0); painter->drawPoint(1, 1); // Round the bottom corner with background color: painter->drawLine(0, height - 1, 0, height - 4); painter->drawLine(1, height - 1, 3, height - 1); painter->drawPoint(1, height - 2); // Surrounding line of the rounded top-left corner: painter->setPen(foregroundPen); painter->drawLine(1, 2, 1, 3); painter->drawLine(2, 1, 3, 1); // Surrounding line of the rounded bottom-left corner: painter->drawLine(1, height - 3, 1, height - 4); painter->drawLine(2, height - 2, 3, height - 2); // Anti-aliased rounded top corner (1/2): painter->setPen(lightForeground); painter->drawPoint(0, 3); painter->drawPoint(3, 0); painter->drawPoint(2, 2); // Anti-aliased rounded bottom corner: painter->drawPoint(0, height - 4); painter->drawPoint(3, height - 1); painter->drawPoint(2, height - 3); // Draw the grips: const qreal middleHeight = (height - 1) / 2; const qreal middleWidth = width / 2; painter->fillRect(middleWidth - 2, middleHeight, 2, 2, foreground); painter->fillRect(middleWidth + 2, middleHeight, 2, 2, foreground); painter->fillRect(middleWidth - 2, middleHeight - 4, 2, 2, foreground); painter->fillRect(middleWidth + 2, middleHeight - 4, 2, 2, foreground); painter->fillRect(middleWidth - 2, middleHeight + 4, 2, 2, foreground); painter->fillRect(middleWidth + 2, middleHeight + 4, 2, 2, foreground); } void Note::drawResizer(QPainter *painter, qreal x, qreal y, qreal width, qreal height, const QColor &background, const QColor &foreground, bool rounded) { const QPen backgroundPen(background); const QPen foregroundPen(foreground); const QColor lightForeground = Tools::mixColor(background, foreground, 2); // Draw the surrounding rectangle: painter->setPen(foregroundPen); painter->fillRect(0, 0, width, height, lightForeground); painter->drawLine(0, 0, width - 2, 0); painter->drawLine(0, height - 1, width - 2, height - 1); painter->drawLine(width - 1, 2, width - 1, height - 2); if (isColumn()) { painter->drawLine(0, 2, 0, height - 2); } if (rounded) { // Round the top corner with background color: painter->setPen(backgroundPen); painter->drawLine(width - 1, 0, width - 3, 0); painter->drawLine(width - 1, 1, width - 1, 2); painter->drawPoint(width - 2, 1); // Round the bottom corner with background color: painter->drawLine(width - 1, height - 1, width - 1, height - 4); painter->drawLine(width - 2, height - 1, width - 4, height - 1); painter->drawPoint(width - 2, height - 2); // Surrounding line of the rounded top-left corner: painter->setPen(foregroundPen); painter->drawLine(width - 2, 2, width - 2, 3); painter->drawLine(width - 3, 1, width - 4, 1); // Surrounding line of the rounded bottom-left corner: painter->drawLine(width - 2, height - 3, width - 2, height - 4); painter->drawLine(width - 3, height - 2, width - 4, height - 2); // Anti-aliased rounded top corner (1/2): painter->setPen(Tools::mixColor(foreground, background, 2)); painter->drawPoint(width - 1, 3); painter->drawPoint(width - 4, 0); // Anti-aliased rounded bottom corner: painter->drawPoint(width - 1, height - 4); painter->drawPoint(width - 4, height - 1); // Anti-aliased rounded top corner (2/2): painter->setPen(foreground); painter->drawPoint(width - 3, 2); // Anti-aliased rounded bottom corner (2/2): painter->drawPoint(width - 3, height - 3); } // Draw the arrows: qreal xArrow = 2; qreal hMargin = 9; int countArrows = (height >= hMargin * 4 + 6 * 3 ? 3 : (height >= hMargin * 3 + 6 * 2 ? 2 : 1)); for (int i = 0; i < countArrows; ++i) { qreal yArrow; switch (countArrows) { default: case 1: yArrow = (height - 6) / 2; break; case 2: yArrow = (i == 1 ? hMargin : height - hMargin - 6); break; case 3: yArrow = (i == 1 ? hMargin : (i == 2 ? (height - 6) / 2 : height - hMargin - 6)); break; } /// Dark color: painter->setPen(foreground); // Left arrow: painter->drawLine(xArrow, yArrow + 2, xArrow + 2, yArrow); painter->drawLine(xArrow, yArrow + 2, xArrow + 2, yArrow + 4); // Right arrow: painter->drawLine(width - 1 - xArrow, yArrow + 2, width - 1 - xArrow - 2, yArrow); painter->drawLine(width - 1 - xArrow, yArrow + 2, width - 1 - xArrow - 2, yArrow + 4); /// Light color: painter->setPen(background); // Left arrow: painter->drawLine(xArrow, yArrow + 2 + 1, xArrow + 2, yArrow + 1); painter->drawLine(xArrow, yArrow + 2 + 1, xArrow + 2, yArrow + 4 + 1); // Right arrow: painter->drawLine(width - 1 - xArrow, yArrow + 2 + 1, width - 1 - xArrow - 2, yArrow + 1); painter->drawLine(width - 1 - xArrow, yArrow + 2 + 1, width - 1 - xArrow - 2, yArrow + 4 + 1); } } void Note::drawInactiveResizer(QPainter *painter, qreal x, qreal y, qreal height, const QColor &background, bool column) { // If background color is too dark, we compute a lighter color instead of a darker: QColor darkBgColor = (Tools::tooDark(background) ? background.light(120) : background.dark(105)); painter->fillRect(x, y, RESIZER_WIDTH, height, Tools::mixColor(background, darkBgColor, 2)); } QPalette Note::palette() const { return (m_basket ? m_basket->palette() : qApp->palette()); } /* type: 1: topLeft * 2: bottomLeft * 3: topRight * 4: bottomRight * 5: fourCorners * 6: noteInsideAndOutsideCorners * (x,y) relate to the painter origin * (width,height) only used for 5:fourCorners type */ void Note::drawRoundings(QPainter *painter, qreal x, qreal y, int type, qreal width, qreal height) { qreal right; switch (type) { case 1: x += this->x(); y += this->y(); basket()->blendBackground(*painter, QRectF(x, y, 4, 1), this->x(), this->y()); basket()->blendBackground(*painter, QRectF(x, y + 1, 2, 1), this->x(), this->y()); basket()->blendBackground(*painter, QRectF(x, y + 2, 1, 1), this->x(), this->y()); basket()->blendBackground(*painter, QRectF(x, y + 3, 1, 1), this->x(), this->y()); break; case 2: x += this->x(); y += this->y(); basket()->blendBackground(*painter, QRectF(x, y - 1, 1, 1), this->x(), this->y()); basket()->blendBackground(*painter, QRectF(x, y, 1, 1), this->x(), this->y()); basket()->blendBackground(*painter, QRectF(x, y + 1, 2, 1), this->x(), this->y()); basket()->blendBackground(*painter, QRectF(x, y + 2, 4, 1), this->x(), this->y()); break; case 3: right = rightLimit(); x += right; y += this->y(); basket()->blendBackground(*painter, QRectF(x - 1, y, 4, 1), right, this->y()); basket()->blendBackground(*painter, QRectF(x + 1, y + 1, 2, 1), right, this->y()); basket()->blendBackground(*painter, QRectF(x + 2, y + 2, 1, 1), right, this->y()); basket()->blendBackground(*painter, QRectF(x + 2, y + 3, 1, 1), right, this->y()); break; case 4: right = rightLimit(); x += right; y += this->y(); basket()->blendBackground(*painter, QRectF(x + 2, y - 1, 1, 1), right, this->y()); basket()->blendBackground(*painter, QRectF(x + 2, y, 1, 1), right, this->y()); basket()->blendBackground(*painter, QRectF(x + 1, y + 1, 2, 1), right, this->y()); basket()->blendBackground(*painter, QRectF(x - 1, y + 2, 4, 1), right, this->y()); break; case 5: // First make sure the corners are white (depending on the widget style): painter->setPen(basket()->backgroundColor()); painter->drawPoint(x, y); painter->drawPoint(x + width - 1, y); painter->drawPoint(x + width - 1, y + height - 1); painter->drawPoint(x, y + height - 1); // And then blend corners: x += this->x(); y += this->y(); basket()->blendBackground(*painter, QRectF(x, y, 1, 1), this->x(), this->y()); basket()->blendBackground(*painter, QRectF(x + width - 1, y, 1, 1), this->x(), this->y()); basket()->blendBackground(*painter, QRectF(x + width - 1, y + height - 1, 1, 1), this->x(), this->y()); basket()->blendBackground(*painter, QRectF(x, y + height - 1, 1, 1), this->x(), this->y()); break; case 6: x += this->x(); y += this->y(); // if (!isSelected()) { // Inside left corners: basket()->blendBackground(*painter, QRectF(x + HANDLE_WIDTH + 1, y + 1, 1, 1), this->x(), this->y()); basket()->blendBackground(*painter, QRectF(x + HANDLE_WIDTH, y + 2, 1, 1), this->x(), this->y()); basket()->blendBackground(*painter, QRectF(x + HANDLE_WIDTH + 1, y + height - 2, 1, 1), this->x(), this->y()); basket()->blendBackground(*painter, QRectF(x + HANDLE_WIDTH, y + height - 3, 1, 1), this->x(), this->y()); // Inside right corners: basket()->blendBackground(*painter, QRectF(x + width - 4, y + 1, 1, 1), this->x(), this->y()); basket()->blendBackground(*painter, QRectF(x + width - 3, y + 2, 1, 1), this->x(), this->y()); basket()->blendBackground(*painter, QRectF(x + width - 4, y + height - 2, 1, 1), this->x(), this->y()); basket()->blendBackground(*painter, QRectF(x + width - 3, y + height - 3, 1, 1), this->x(), this->y()); //} // Outside right corners: basket()->blendBackground(*painter, QRectF(x + width - 1, y, 1, 1), this->x(), this->y()); basket()->blendBackground(*painter, QRectF(x + width - 1, y + height - 1, 1, 1), this->x(), this->y()); break; } } /// Blank Spaces Drawing: void Note::setOnTop(bool onTop) { setZValue(onTop ? 100 : 0); m_onTop = onTop; Note *note = firstChild(); while (note) { note->setOnTop(onTop); note = note->next(); } } void substractRectOnAreas(const QRectF &rectToSubstract, QList &areas, bool andRemove) { for (int i = 0; i < areas.size();) { QRectF &rect = areas[i]; // Split the rectangle if it intersects with rectToSubstract: if (rect.intersects(rectToSubstract)) { // Create the top rectangle: if (rectToSubstract.top() > rect.top()) { areas.insert(i++, QRectF(rect.left(), rect.top(), rect.width(), rectToSubstract.top() - rect.top())); rect.setTop(rectToSubstract.top()); } // Create the bottom rectangle: if (rectToSubstract.bottom() < rect.bottom()) { areas.insert(i++, QRectF(rect.left(), rectToSubstract.bottom(), rect.width(), rect.bottom() - rectToSubstract.bottom())); rect.setBottom(rectToSubstract.bottom()); } // Create the left rectangle: if (rectToSubstract.left() > rect.left()) { areas.insert(i++, QRectF(rect.left(), rect.top(), rectToSubstract.left() - rect.left(), rect.height())); rect.setLeft(rectToSubstract.left()); } // Create the right rectangle: if (rectToSubstract.right() < rect.right()) { areas.insert(i++, QRectF(rectToSubstract.right(), rect.top(), rect.right() - rectToSubstract.right(), rect.height())); rect.setRight(rectToSubstract.right()); } // Remove the rectangle if it's entirely contained: if (andRemove && rectToSubstract.contains(rect)) areas.removeAt(i); else ++i; } else ++i; } } void Note::recomputeAreas() { // Initialize the areas with the note rectangle(s): m_areas.clear(); m_areas.append(visibleRect()); if (hasResizer()) m_areas.append(resizerRect()); // Cut the areas where other notes are on top of this note: Note *note = basket()->firstNote(); bool noteIsAfterThis = false; while (note) { noteIsAfterThis = recomputeAreas(note, noteIsAfterThis); note = note->next(); } } bool Note::recomputeAreas(Note *note, bool noteIsAfterThis) { if (note == this) noteIsAfterThis = true; // Only compute overlapping of notes AFTER this, or ON TOP this: // else if ( note->matching() && noteIsAfterThis && (!isOnTop() || (isOnTop() && note->isOnTop())) || (!isOnTop() && note->isOnTop()) ) { else if (note->matching() && noteIsAfterThis && ((!(isOnTop() || isEditing()) || ((isOnTop() || isEditing()) && (note->isOnTop() || note->isEditing()))) || (!(isOnTop() || isEditing()) && (note->isOnTop() || note->isEditing())))) { // if (!(isSelected() && !note->isSelected())) { // FIXME: FIXME: FIXME: FIXME: This last condition was added LATE, so we should look if it's ALWAYS good: substractRectOnAreas(note->visibleRect(), m_areas, true); if (note->hasResizer()) substractRectOnAreas(note->resizerRect(), m_areas, true); //} } if (note->isGroup()) { Note *child = note->firstChild(); bool first = true; while (child) { if ((note->showSubNotes() || first) && note->matching()) noteIsAfterThis = recomputeAreas(child, noteIsAfterThis); child = child->next(); first = false; } } return noteIsAfterThis; } bool Note::isEditing() { return basket()->editedNote() == this; } /* Drawing policy: * ============== * - Draw the note on a pixmap and then draw the pixmap on screen is faster and * flicker-free, rather than drawing directly on screen * - The next time the pixmap can be directly redrawn on screen without * (relatively low, for small texts) time-consuming text-drawing * - To keep memory footprint low, we can destruct the bufferPixmap because * redrawing it offscreen and applying it onscreen is nearly as fast as just * drawing the pixmap onscreen * - But as drawing the pixmap offscreen is little time consuming we can keep * last visible notes buffered and then the redraw of the entire window is * INSTANTANEOUS * - We keep buffered note/group draws BUT NOT the resizer: such objects are * small and fast to draw, so we don't complexify code for that */ void Note::draw(QPainter *painter, const QRectF & /*clipRect*/) { if (!matching()) return; /** Compute visible areas: */ if (!m_computedAreas) recomputeAreas(); if (m_areas.isEmpty()) return; /** Directly draw pixmap on screen if it is already buffered: */ if (isBufferized()) { drawBufferOnScreen(painter, m_bufferedPixmap); return; } /** If pixmap is Null (size 0), no point in painting: **/ if (!width() || !height()) return; /** Initialise buffer painter: */ m_bufferedPixmap = QPixmap(width(), height()); Q_ASSERT(!m_bufferedPixmap.isNull()); QPainter painter2(&m_bufferedPixmap); /** Initialise colors: */ QColor baseColor(basket()->backgroundColor()); QColor highColor(palette().color(QPalette::Highlight)); QColor midColor = Tools::mixColor(baseColor, highColor, 2); /** Initialise brushes and pens: */ QBrush baseBrush(baseColor); QBrush highBrush(highColor); QPen basePen(baseColor); QPen highPen(highColor); QPen midPen(midColor); /** Figure out the state of the note: */ bool hovered = m_hovered && m_hoveredZone != TopInsert && m_hoveredZone != BottomInsert && m_hoveredZone != Resizer; /** And then draw the group: */ if (isGroup()) { // Draw background or handle: if (hovered) { drawHandle(&painter2, 0, 0, width(), height(), baseColor, highColor, midColor); drawRoundings(&painter2, 0, 0, /*type=*/1); drawRoundings(&painter2, 0, height() - 3, /*type=*/2); } else { painter2.fillRect(0, 0, width(), height(), baseBrush); basket()->blendBackground(painter2, boundingRect().translated(x(), y()), -1, -1, /*opaque=*/true); } // Draw expander: qreal yExp = yExpander(); drawExpander(&painter2, NOTE_MARGIN, yExp, hovered ? midColor : baseColor, m_isFolded, basket()); // Draw expander rounded edges: if (hovered) { QColor color1 = expanderBackground(height(), yExp, highColor); QColor color2 = expanderBackground(height(), yExp + EXPANDER_HEIGHT - 1, highColor); painter2.setPen(color1); painter2.drawPoint(NOTE_MARGIN, yExp); painter2.drawPoint(NOTE_MARGIN + 9 - 1, yExp); painter2.setPen(color2); painter2.drawPoint(NOTE_MARGIN, yExp + 9 - 1); painter2.drawPoint(NOTE_MARGIN + 9 - 1, yExp + 9 - 1); } else drawRoundings(&painter2, NOTE_MARGIN, yExp, /*type=*/5, 9, 9); // Draw on screen: painter2.end(); drawBufferOnScreen(painter, m_bufferedPixmap); return; } /** Or draw the note: */ // What are the background colors: QColor background; if (m_computedState.backgroundColor().isValid()) { background = m_computedState.backgroundColor(); } else { background = basket()->backgroundColor(); } if (!hovered && !isSelected()) { // Draw background: painter2.fillRect(0, 0, width(), height(), background); basket()->blendBackground(painter2, boundingRect().translated(x(), y())); } else { // Draw selection background: painter2.fillRect(0, 0, width(), height(), midColor); // Top/Bottom lines: painter2.setPen(highPen); painter2.drawLine(0, height() - 1, width(), height() - 1); painter2.drawLine(0, 0, width(), 0); // The handle: drawHandle(&painter2, 0, 0, HANDLE_WIDTH, height(), baseColor, highColor, midColor); drawRoundings(&painter2, 0, 0, /*type=*/1); drawRoundings(&painter2, 0, height() - 3, /*type=*/2); painter2.setPen(midColor); drawRoundings(&painter2, 0, 0, /*type=*/6, width(), height()); } // Draw the Emblems: qreal yIcon = (height() - EMBLEM_SIZE) / 2; qreal xIcon = HANDLE_WIDTH + NOTE_MARGIN; for (State::List::Iterator it = m_states.begin(); it != m_states.end(); ++it) { if (!(*it)->emblem().isEmpty()) { QPixmap stateEmblem = KIconLoader::global()->loadIcon((*it)->emblem(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, false); painter2.drawPixmap(xIcon, yIcon, stateEmblem); xIcon += NOTE_MARGIN + EMBLEM_SIZE; } } // Determine the colors (for the richText drawing) and the text color (for the tags arrow too): QPalette notePalette(basket()->palette()); notePalette.setColor(QPalette::Text, (m_computedState.textColor().isValid() ? m_computedState.textColor() : basket()->textColor())); notePalette.setColor(QPalette::Background, background); if (isSelected()) notePalette.setColor(QPalette::Text, palette().color(QPalette::HighlightedText)); // Draw the Tags Arrow: if (hovered) { QColor textColor = notePalette.color(QPalette::Text); QColor light = Tools::mixColor(textColor, background); QColor mid = Tools::mixColor(textColor, light); painter2.setPen(light); // QPen(basket()->colorGroup().dark().light(150))); painter2.drawLine(xIcon, yIcon + 6, xIcon + 4, yIcon + 6); painter2.setPen(mid); // QPen(basket()->colorGroup().dark())); painter2.drawLine(xIcon + 1, yIcon + 7, xIcon + 3, yIcon + 7); painter2.setPen(textColor); // QPen(basket()->colorGroup().foreground())); painter2.drawPoint(xIcon + 2, yIcon + 8); } else if (m_haveInvisibleTags) { painter2.setPen(notePalette.color(QPalette::Text) /*QPen(basket()->colorGroup().foreground())*/); painter2.drawPoint(xIcon, yIcon + 7); painter2.drawPoint(xIcon + 2, yIcon + 7); painter2.drawPoint(xIcon + 4, yIcon + 7); } // Draw on screen: painter2.end(); drawBufferOnScreen(painter, m_bufferedPixmap); } void Note::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { if (!m_basket->isLoaded()) return; if (boundingRect().width() <= 0.1 || boundingRect().height() <= 0.1) return; draw(painter, boundingRect()); if (hasResizer()) { qreal right = rightLimit() - x(); QRectF resizerRect(0, 0, RESIZER_WIDTH, resizerHeight()); // Prepare to draw the resizer: QPixmap pixmap(RESIZER_WIDTH, resizerHeight()); QPainter painter2(&pixmap); // Draw gradient or resizer: if ((m_hovered && m_hoveredZone == Resizer) || ((m_hovered || isSelected()) && !isColumn())) { QColor baseColor(basket()->backgroundColor()); QColor highColor(palette().color(QPalette::Highlight)); drawResizer(&painter2, 0, 0, RESIZER_WIDTH, resizerHeight(), baseColor, highColor, /*rounded=*/!isColumn()); if (!isColumn()) { drawRoundings(&painter2, RESIZER_WIDTH - 3, 0, /*type=*/3); drawRoundings(&painter2, RESIZER_WIDTH - 3, resizerHeight() - 3, /*type=*/4); } } else { drawInactiveResizer(&painter2, /*x=*/0, /*y=*/0, /*height=*/resizerHeight(), basket()->backgroundColor(), isColumn()); resizerRect.translate(rightLimit(), y()); basket()->blendBackground(painter2, resizerRect); } // Draw on screen: painter2.end(); painter->drawPixmap(right, 0, pixmap); } } void Note::drawBufferOnScreen(QPainter *painter, const QPixmap &contentPixmap) { for (QList::iterator it = m_areas.begin(); it != m_areas.end(); ++it) { QRectF rect = (*it).translated(-x(), -y()); if (rect.x() >= width()) // It's a rect of the resizer, don't draw it! continue; painter->drawPixmap(rect.x(), rect.y(), contentPixmap, rect.x(), rect.y(), rect.width(), rect.height()); } } void Note::setContent(NoteContent *content) { m_content = content; } /*const */ State::List &Note::states() const { return (State::List &)m_states; } void Note::addState(State *state, bool orReplace) { if (!content()) return; Tag *tag = state->parentTag(); State::List::iterator itStates = m_states.begin(); // Browse all tags, see if the note has it, increment itSates if yes, and then insert the state at this position... // For each existing tags: for (Tag::List::iterator it = Tag::all.begin(); it != Tag::all.end(); ++it) { // If the current tag isn't the one to assign or the current one on the note, go to the next tag: if (*it != tag && itStates != m_states.end() && *it != (*itStates)->parentTag()) continue; // We found the tag to insert: if (*it == tag) { // And the note already have the tag: if (itStates != m_states.end() && *it == (*itStates)->parentTag()) { // We replace the state if wanted: if (orReplace) { itStates = m_states.insert(itStates, state); ++itStates; m_states.erase(itStates); recomputeStyle(); } } else { m_states.insert(itStates, state); recomputeStyle(); } return; } // The note has this tag: if (itStates != m_states.end() && *it == (*itStates)->parentTag()) ++itStates; } } QFont Note::font() { return m_computedState.font(basket()->QGraphicsScene::font()); } QColor Note::backgroundColor() { if (m_computedState.backgroundColor().isValid()) return m_computedState.backgroundColor(); else return basket()->backgroundColor(); } QColor Note::textColor() { if (m_computedState.textColor().isValid()) return m_computedState.textColor(); else return basket()->textColor(); } void Note::recomputeStyle() { State::merge(m_states, &m_computedState, &m_emblemsCount, &m_haveInvisibleTags, basket()->backgroundColor()); // unsetWidth(); if (content()) { if (content()->graphicsItem()) content()->graphicsItem()->setPos(contentX(), NOTE_MARGIN); content()->fontChanged(); } // requestRelayout(); // TODO! } void Note::recomputeAllStyles() { if (content()) // We do the merge ourself, without calling recomputeStyle(), so there is no infinite recursion: // State::merge(m_states, &m_computedState, &m_emblemsCount, &m_haveInvisibleTags, basket()->backgroundColor()); recomputeStyle(); else if (isGroup()) FOR_EACH_CHILD(child) child->recomputeAllStyles(); } bool Note::removedStates(const QList &deletedStates) { bool modifiedBasket = false; if (!states().isEmpty()) { for (QList::const_iterator it = deletedStates.begin(); it != deletedStates.end(); ++it) if (hasState(*it)) { removeState(*it); modifiedBasket = true; } } FOR_EACH_CHILD(child) if (child->removedStates(deletedStates)) modifiedBasket = true; return modifiedBasket; } void Note::addTag(Tag *tag) { addState(tag->states().first(), /*but do not replace:*/ false); } void Note::removeState(State *state) { for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it) if (*it == state) { m_states.erase(it); recomputeStyle(); return; } } void Note::removeTag(Tag *tag) { for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it) if ((*it)->parentTag() == tag) { m_states.erase(it); recomputeStyle(); return; } } void Note::removeAllTags() { m_states.clear(); recomputeStyle(); } void Note::addTagToSelectedNotes(Tag *tag) { if (content() && isSelected()) addTag(tag); FOR_EACH_CHILD(child) child->addTagToSelectedNotes(tag); } void Note::removeTagFromSelectedNotes(Tag *tag) { if (content() && isSelected()) { if (hasTag(tag)) setWidth(0); removeTag(tag); } FOR_EACH_CHILD(child) child->removeTagFromSelectedNotes(tag); } void Note::removeAllTagsFromSelectedNotes() { if (content() && isSelected()) { if (m_states.count() > 0) setWidth(0); removeAllTags(); } FOR_EACH_CHILD(child) child->removeAllTagsFromSelectedNotes(); } void Note::addStateToSelectedNotes(State *state, bool orReplace) { if (content() && isSelected()) addState(state, orReplace); FOR_EACH_CHILD(child) child->addStateToSelectedNotes(state, orReplace); // TODO: BasketScene::addStateToSelectedNotes() does not have orReplace } void Note::changeStateOfSelectedNotes(State *state) { if (content() && isSelected() && hasTag(state->parentTag())) addState(state); FOR_EACH_CHILD(child) child->changeStateOfSelectedNotes(state); } bool Note::selectedNotesHaveTags() { if (content() && isSelected() && m_states.count() > 0) return true; FOR_EACH_CHILD(child) if (child->selectedNotesHaveTags()) return true; return false; } bool Note::hasState(State *state) { for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it) if (*it == state) return true; return false; } bool Note::hasTag(Tag *tag) { for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it) if ((*it)->parentTag() == tag) return true; return false; } State *Note::stateOfTag(Tag *tag) { for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it) if ((*it)->parentTag() == tag) return *it; return 0; } bool Note::allowCrossReferences() { for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it) if (!(*it)->allowCrossReferences()) return false; return true; } State *Note::stateForEmblemNumber(int number) const { int i = -1; for (State::List::const_iterator it = m_states.begin(); it != m_states.end(); ++it) if (!(*it)->emblem().isEmpty()) { ++i; if (i == number) return *it; } return 0; } bool Note::stateForTagFromSelectedNotes(Tag *tag, State **state) { if (content() && isSelected()) { // What state is the tag on this note? State *stateOfTag = this->stateOfTag(tag); // This tag is not assigned to this note, the action will assign it, then: if (stateOfTag == 0) *state = 0; else { // Take the LOWEST state of all the selected notes: // Say the two selected notes have the state "Done" and "To Do". // The first note set *state to "Done". // When reaching the second note, we should recognize "To Do" is first in the tag states, then take it // Because pressing the tag shortcut key should first change state before removing the tag! if (*state == 0) *state = stateOfTag; else { bool stateIsFirst = true; for (State *nextState = stateOfTag->nextState(); nextState; nextState = nextState->nextState(/*cycle=*/false)) if (nextState == *state) stateIsFirst = false; if (!stateIsFirst) *state = stateOfTag; } } return true; // We encountered a selected note } bool encounteredSelectedNote = false; FOR_EACH_CHILD(child) { bool encountered = child->stateForTagFromSelectedNotes(tag, state); if (encountered && *state == 0) return true; if (encountered) encounteredSelectedNote = true; } return encounteredSelectedNote; } void Note::inheritTagsOf(Note *note) { if (!note || !content()) return; for (State::List::iterator it = note->states().begin(); it != note->states().end(); ++it) if ((*it)->parentTag() && (*it)->parentTag()->inheritedBySiblings()) addTag((*it)->parentTag()); } void Note::unbufferizeAll() { unbufferize(); if (isGroup()) { Note *child = firstChild(); while (child) { child->unbufferizeAll(); child = child->next(); } } } QRectF Note::visibleRect() { QList areas; areas.append(QRectF(x(), y(), width(), height())); // When we are folding a parent group, if this note is bigger than the first real note of the group, cut the top of this: /*Note *parent = parentNote(); while (parent) { if (parent->expandingOrCollapsing()) substractRectOnAreas(QRect(x(), parent->y() - height(), width(), height()), areas, true); parent = parent->parentNote(); }*/ if (areas.count() > 0) return areas.first(); else return QRectF(); } void Note::recomputeBlankRects(QList &blankAreas) { if (!matching()) return; // visibleRect() instead of rect() because if we are folding/expanding a smaller parent group, then some part is hidden! // But anyway, a resizer is always a primary note and is never hidden by a parent group, so no visibleResizerRect() method! substractRectOnAreas(visibleRect(), blankAreas, true); if (hasResizer()) substractRectOnAreas(resizerRect(), blankAreas, true); if (isGroup()) { Note *child = firstChild(); bool first = true; while (child) { if ((showSubNotes() || first) && child->matching()) child->recomputeBlankRects(blankAreas); child = child->next(); first = false; } } } void Note::linkLookChanged() { if (isGroup()) { Note *child = firstChild(); while (child) { child->linkLookChanged(); child = child->next(); } } else content()->linkLookChanged(); } Note *Note::noteForFullPath(const QString &path) { if (content() && fullPath() == path) return this; Note *child = firstChild(); Note *found; while (child) { found = child->noteForFullPath(path); if (found) return found; child = child->next(); } return 0; } void Note::listUsedTags(QList &list) { for (State::List::Iterator it = m_states.begin(); it != m_states.end(); ++it) { Tag *tag = (*it)->parentTag(); if (!list.contains(tag)) list.append(tag); } FOR_EACH_CHILD(child) child->listUsedTags(list); } void Note::usedStates(QList &states) { if (content()) for (State::List::Iterator it = m_states.begin(); it != m_states.end(); ++it) if (!states.contains(*it)) states.append(*it); FOR_EACH_CHILD(child) child->usedStates(states); } Note *Note::nextInStack() { // First, search in the children: if (firstChild()) { if (firstChild()->content()) return firstChild(); else return firstChild()->nextInStack(); } // Then, in the next: if (next()) { if (next()->content()) return next(); else return next()->nextInStack(); } // And finally, in the parent: Note *note = parentNote(); while (note) if (note->next()) if (note->next()->content()) return note->next(); else return note->next()->nextInStack(); else note = note->parentNote(); // Not found: return 0; } Note *Note::prevInStack() { // First, search in the previous: if (prev() && prev()->content()) return prev(); // Else, it's a group, get the last item in that group: if (prev()) { Note *note = prev()->lastRealChild(); if (note) return note; } if (parentNote()) return parentNote()->prevInStack(); else return 0; } Note *Note::nextShownInStack() { Note *next = nextInStack(); while (next && !next->isShown()) next = next->nextInStack(); return next; } Note *Note::prevShownInStack() { Note *prev = prevInStack(); while (prev && !prev->isShown()) prev = prev->prevInStack(); return prev; } bool Note::isShown() { // First, the easy one: groups are always shown: if (isGroup()) return true; // And another easy part: non-matching notes are hidden: if (!matching()) return false; if (basket()->isFiltering()) // And isMatching() because of the line above! return true; // So, here we go to the complex case: if the note is inside a collapsed group: Note *group = parentNote(); Note *child = this; while (group) { if (group->isFolded() && group->firstChild() != child) return false; child = group; group = group->parentNote(); } return true; } void Note::debug() { qDebug() << "Note@" << (quint64)this; if (!this) { qDebug(); return; } if (isColumn()) qDebug() << ": Column"; else if (isGroup()) qDebug() << ": Group"; else qDebug() << ": Content[" << content()->lowerTypeName() << "]: " << toText(""); qDebug(); } Note *Note::firstSelected() { if (isSelected()) return this; Note *first = 0; FOR_EACH_CHILD(child) { first = child->firstSelected(); if (first) break; } return first; } Note *Note::lastSelected() { if (isSelected()) return this; Note *last = 0, *tmp = 0; FOR_EACH_CHILD(child) { tmp = child->lastSelected(); if (tmp) last = tmp; } return last; } Note *Note::selectedGroup() { if (isGroup() && allSelected() && count() == basket()->countSelecteds()) return this; FOR_EACH_CHILD(child) { Note *selectedGroup = child->selectedGroup(); if (selectedGroup) return selectedGroup; } return 0; } void Note::groupIn(Note *group) { if (this == group) return; if (allSelected() && !isColumn()) { basket()->unplugNote(this); basket()->insertNote(this, group, Note::BottomColumn, QPointF(), /*animateNewPosition=*/true); } else { Note *next; Note *child = firstChild(); while (child) { next = child->next(); child->groupIn(group); child = next; } } } bool Note::tryExpandParent() { Note *parent = parentNote(); Note *child = this; while (parent) { if (parent->firstChild() != child) return false; if (parent->isColumn()) return false; if (parent->isFolded()) { parent->toggleFolded(); basket()->relayoutNotes(true); return true; } child = parent; parent = parent->parentNote(); } return false; } bool Note::tryFoldParent() // TODO: withCtrl ? withShift ? { Note *parent = parentNote(); Note *child = this; while (parent) { if (parent->firstChild() != child) return false; if (parent->isColumn()) return false; if (!parent->isFolded()) { parent->toggleFolded(); basket()->relayoutNotes(true); return true; } child = parent; parent = parent->parentNote(); } return false; } qreal Note::distanceOnLeftRight(Note *note, int side) { if (side == BasketScene::RIGHT_SIDE) { // If 'note' is on left of 'this': cannot switch from this to note by pressing Right key: if (x() > note->x() || finalRightLimit() > note->finalRightLimit()) return -1; } else { /*LEFT_SIDE:*/ // If 'note' is on left of 'this': cannot switch from this to note by pressing Right key: if (x() < note->x() || finalRightLimit() < note->finalRightLimit()) return -1; } if (x() == note->x() && finalRightLimit() == note->finalRightLimit()) return -1; qreal thisCenterX = x() + (side == BasketScene::LEFT_SIDE ? width() : /*RIGHT_SIDE:*/ 0); qreal thisCenterY = y() + height() / 2; qreal noteCenterX = note->x() + note->width() / 2; qreal noteCenterY = note->y() + note->height() / 2; if (thisCenterY > note->bottom()) noteCenterY = note->bottom(); else if (thisCenterY < note->y()) noteCenterY = note->y(); else noteCenterY = thisCenterY; qreal angle = 0; if (noteCenterX - thisCenterX != 0) angle = 1000 * ((noteCenterY - thisCenterY) / (noteCenterX - thisCenterX)); if (angle < 0) angle = -angle; return sqrt(pow(noteCenterX - thisCenterX, 2) + pow(noteCenterY - thisCenterY, 2)) + angle; } qreal Note::distanceOnTopBottom(Note *note, int side) { if (side == BasketScene::BOTTOM_SIDE) { // If 'note' is on left of 'this': cannot switch from this to note by pressing Right key: if (y() > note->y() || bottom() > note->bottom()) return -1; } else { /*TOP_SIDE:*/ // If 'note' is on left of 'this': cannot switch from this to note by pressing Right key: if (y() < note->y() || bottom() < note->bottom()) return -1; } if (y() == note->y() && bottom() == note->bottom()) return -1; qreal thisCenterX = x() + width() / 2; qreal thisCenterY = y() + (side == BasketScene::TOP_SIDE ? height() : /*BOTTOM_SIDE:*/ 0); qreal noteCenterX = note->x() + note->width() / 2; qreal noteCenterY = note->y() + note->height() / 2; if (thisCenterX > note->finalRightLimit()) noteCenterX = note->finalRightLimit(); else if (thisCenterX < note->x()) noteCenterX = note->x(); else noteCenterX = thisCenterX; qreal angle = 0; if (noteCenterX - thisCenterX != 0) angle = 1000 * ((noteCenterY - thisCenterY) / (noteCenterX - thisCenterX)); if (angle < 0) angle = -angle; return sqrt(pow(noteCenterX - thisCenterX, 2) + pow(noteCenterY - thisCenterY, 2)) + angle; } Note *Note::parentPrimaryNote() { Note *primary = this; while (primary->parentNote()) primary = primary->parentNote(); return primary; } void Note::deleteChilds() { Note *child = firstChild(); while (child) { Note *tmp = child->next(); delete child; child = tmp; } } bool Note::saveAgain() { bool result = true; if (content()) { if (!content()->saveToFile()) result = false; } FOR_EACH_CHILD(child) { if (!child->saveAgain()) result = false; } if (!result) DEBUG_WIN << QString("Note::saveAgain returned false for %1:%2").arg((content() != NULL) ? content()->typeName() : "null", toText("")); return result; } bool Note::convertTexts() { bool convertedNotes = false; if (content() && content()->lowerTypeName() == "text") { QString text = ((TextContent *)content())->text(); QString html = "" + Tools::textToHTMLWithoutP(text) + ""; basket()->saveToFile(fullPath(), html); setContent(new HtmlContent(this, content()->fileName())); convertedNotes = true; } FOR_EACH_CHILD(child) if (child->convertTexts()) convertedNotes = true; return convertedNotes; } /* vim: set et sts=4 sw=4 ts=16 tw=78 : */ /* kate: indent-width 4; replace-tabs on; */ diff --git a/src/note.h b/src/note.h index 6a4a46a..0653915 100644 --- a/src/note.h +++ b/src/note.h @@ -1,438 +1,425 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef NOTE_H #define NOTE_H #include #include #include #include #include "basket_export.h" #include "tag.h" class BasketScene; class FilterData; class NoteContent; class NoteSelection; class QPainter; class QPixmap; class QString; class QGraphicsItemAnimation; class QTimeLine; class NotePrivate; /** Handle basket notes and groups!\n * After creation, the note is a group. You should create a NoteContent with this Note * as constructor parameter to transform it to a note with content. eg: * @code * Note *note = new Note(basket); // note is a group! * new TextContent(note, fileName); // note is now a note with a text content! * new ColorContent(note, Qt::red); // Should never be done!!!!! the old Content should be deleted... * @endcode * @author Sébastien Laoût */ class BASKET_EXPORT Note : public QGraphicsItemGroup { /// CONSTRUCTOR AND DESTRUCTOR: public: explicit Note(BasketScene *parent = nullptr); ~Note() override; private: NotePrivate *d; /// DOUBLY LINKED LIST: public: void setNext(Note *next); void setPrev(Note *prev); Note *next() const; Note *prev() const; public: void setWidth(qreal width); void setWidthForceRelayout(qreal width); //! Do not use it unless you know what you do! void setInitialHeight(qreal height); void setXRecursively(qreal ax); void setYRecursively(qreal ay); void hideRecursively(); qreal width() const; qreal height() const; qreal bottom() const; QRectF rect(); QRectF resizerRect(); QRectF visibleRect(); QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; void relayoutAt(qreal ax, qreal ay, bool animate); qreal contentX() const; qreal minWidth() const; qreal minRight(); void unsetWidth(); void requestRelayout(); /** << DO NEVER USE IT!!! Only available when moving notes, groups should be recreated with the exact same state as before! */ void setHeight(qreal height); /// FREE AND COLUMN LAYOUTS MANAGEMENT: private: qreal m_groupWidth; public: qreal groupWidth() const; void setGroupWidth(qreal width); qreal rightLimit() const; qreal finalRightLimit() const; bool isFree() const; bool isColumn() const; bool hasResizer() const; qreal resizerHeight() const; /// GROUPS MANAGEMENT: private: bool m_isFolded; Note *m_firstChild; Note *m_parentNote; public: inline bool isGroup() const { return m_content == 0L; } inline bool isFolded() { return m_isFolded; } inline Note *firstChild() { return m_firstChild; } inline Note *parentNote() const { return m_parentNote; } /*inline*/ bool showSubNotes(); // { return !m_isFolded || !m_collapseFinished; } inline void setParentNote(Note *note) { m_parentNote = note; } inline void setFirstChild(Note *note) { m_firstChild = note; } bool isShown(); bool toggleFolded(); Note *noteAt(QPointF pos); Note *firstRealChild(); Note *lastRealChild(); Note *lastChild(); Note *lastSibling(); qreal yExpander(); bool isAfter(Note *note); bool containsNote(Note *note); /// NOTES VARIOUS PROPERTIES: // CONTENT MANAGEMENT? private: BasketScene *m_basket; NoteContent *m_content; QDateTime m_addedDate; QDateTime m_lastModificationDate; public: inline BasketScene *basket() const { return m_basket; } inline NoteContent *content() { return m_content; } inline void setAddedDate(const QDateTime &dateTime) { m_addedDate = dateTime; } inline void setLastModificationDate(const QDateTime &dateTime) { m_lastModificationDate = dateTime; } void setParentBasket(BasketScene *basket); QDateTime addedDate() { return m_addedDate; } QDateTime lastModificationDate() { return m_lastModificationDate; } QString addedStringDate(); QString lastModificationStringDate(); QString toText(const QString &cuttedFullPath); bool saveAgain(); void deleteChilds(); protected: void setContent(NoteContent *content); friend class NoteContent; friend class AnimationContent; /// DRAWING: private: QPixmap m_bufferedPixmap; QPixmap m_bufferedSelectionPixmap; public: void draw(QPainter *painter, const QRectF &clipRect); void drawBufferOnScreen(QPainter *painter, const QPixmap &contentPixmap); static void getGradientColors(const QColor &originalBackground, QColor *colorTop, QColor *colorBottom); static void drawExpander(QPainter *painter, qreal x, qreal y, const QColor &background, bool expand, BasketScene *basket); void drawHandle(QPainter *painter, qreal x, qreal y, qreal width, qreal height, const QColor &background, const QColor &foreground, const QColor &lightForeground); void drawResizer(QPainter *painter, qreal x, qreal y, qreal width, qreal height, const QColor &background, const QColor &foreground, bool rounded); void drawRoundings(QPainter *painter, qreal x, qreal y, int type, qreal width = 0, qreal height = 0); void unbufferizeAll(); inline void unbufferize() { m_bufferedPixmap = QPixmap(); m_bufferedSelectionPixmap = QPixmap(); } inline bool isBufferized() { return !m_bufferedPixmap.isNull(); } void recomputeBlankRects(QList &blankAreas); static void drawInactiveResizer(QPainter *painter, qreal x, qreal y, qreal height, const QColor &background, bool column); QPalette palette() const; /// VISIBLE AREAS COMPUTATION: private: QList m_areas; bool m_computedAreas; bool m_onTop; void recomputeAreas(); bool recomputeAreas(Note *note, bool noteIsAfterThis); public: void setOnTop(bool onTop); inline bool isOnTop() { return m_onTop; } bool isEditing(); /// MANAGE ANIMATION: private: QGraphicsItemAnimation *m_animation; public: bool initAnimationLoad(QTimeLine *timeLine); void animationFinished(); /// USER INTERACTION: public: enum Zone { None = 0, Handle, TagsArrow, Custom0, /*CustomContent1, CustomContent2, */ Content, Link, TopInsert, TopGroup, BottomInsert, BottomGroup, BottomColumn, Resizer, Group, GroupExpander, Emblem0 }; // Emblem0 should be at end, because we add 1 to get Emblem1, 2 to get Emblem2... inline void setHovered(bool hovered) { m_hovered = hovered; unbufferize(); } void setHoveredZone(Zone zone); inline bool hovered() { return m_hovered; } inline Zone hoveredZone() { return m_hoveredZone; } Zone zoneAt(const QPointF &pos, bool toAdd = false); QRectF zoneRect(Zone zone, const QPointF &pos); Qt::CursorShape cursorFromZone(Zone zone) const; QString linkAt(const QPointF &pos); private: bool m_hovered; Zone m_hoveredZone; /// SELECTION: public: NoteSelection *selectedNotes(); void setSelected(bool selected); void setSelectedRecursively(bool selected); void invertSelectionRecursively(); void selectIn(const QRectF &rect, bool invertSelection, bool unselectOthers = true); void setFocused(bool focused); inline bool isFocused() { return m_focused; } inline bool isSelected() { return m_selected; } bool allSelected(); void resetWasInLastSelectionRect(); void unselectAllBut(Note *toSelect); void invertSelectionOf(Note *toSelect); Note *theSelectedNote(); private: bool m_focused; bool m_selected; bool m_wasInLastSelectionRect; /// TAGS: private: State::List m_states; State m_computedState; int m_emblemsCount; bool m_haveInvisibleTags; public: /*const */ State::List &states() const; inline int emblemsCount() { return m_emblemsCount; } void addState(State *state, bool orReplace = true); void addTag(Tag *tag); void removeState(State *state); void removeTag(Tag *tag); void removeAllTags(); void addTagToSelectedNotes(Tag *tag); void removeTagFromSelectedNotes(Tag *tag); void removeAllTagsFromSelectedNotes(); void addStateToSelectedNotes(State *state, bool orReplace = true); void changeStateOfSelectedNotes(State *state); bool selectedNotesHaveTags(); void inheritTagsOf(Note *note); bool hasTag(Tag *tag); bool hasState(State *state); State *stateOfTag(Tag *tag); State *stateForEmblemNumber(int number) const; bool stateForTagFromSelectedNotes(Tag *tag, State **state); void recomputeStyle(); void recomputeAllStyles(); bool removedStates(const QList &deletedStates); QFont font(); // Computed! QColor backgroundColor(); // Computed! QColor textColor(); // Computed! bool allowCrossReferences(); /// FILTERING: private: bool m_matching; public: bool computeMatching(const FilterData &data); int newFilter(const FilterData &data); bool matching() { return m_matching; } /// ADDED: public: /** * @return true if this note could be deleted **/ void deleteSelectedNotes(bool deleteFilesToo = true, QSet *notesToBeDeleted = 0); int count(); int countDirectChilds(); QString fullPath(); Note *noteForFullPath(const QString &path); // void update(); void linkLookChanged(); void usedStates(QList &states); void listUsedTags(QList &list); Note *nextInStack(); Note *prevInStack(); Note *nextShownInStack(); Note *prevShownInStack(); Note *parentPrimaryNote(); // TODO: There are places in the code where this methods is hand-copied / hand-inlined instead of called. Note *firstSelected(); Note *lastSelected(); Note *selectedGroup(); void groupIn(Note *group); bool tryExpandParent(); bool tryFoldParent(); qreal distanceOnLeftRight(Note *note, int side); qreal distanceOnTopBottom(Note *note, int side); bool convertTexts(); void debug(); /// SPEED OPTIMIZATION public: void finishLazyLoad(); public: // Values are provided here as info: // Please see Settings::setBigNotes() to know whats values are assigned. static qreal NOTE_MARGIN /*= 2*/; static qreal INSERTION_HEIGHT /*= 5*/; static qreal EXPANDER_WIDTH /*= 9*/; static qreal EXPANDER_HEIGHT /*= 9*/; static qreal GROUP_WIDTH /*= 2*NOTE_MARGIN + EXPANDER_WIDTH*/; static qreal HANDLE_WIDTH /*= GROUP_WIDTH*/; static qreal RESIZER_WIDTH /*= GROUP_WIDTH*/; static qreal TAG_ARROW_WIDTH /*= 5*/; static qreal EMBLEM_SIZE /*= 16*/; static qreal MIN_HEIGHT /*= 2*NOTE_MARGIN + EMBLEM_SIZE*/; }; /* * Convenience functions: */ extern void substractRectOnAreas(const QRectF &rectToSubstract, QList &areas, bool andRemove = true); #endif // NOTE_H diff --git a/src/notecontent.cpp b/src/notecontent.cpp index f8c1034..d86f16f 100644 --- a/src/notecontent.cpp +++ b/src/notecontent.cpp @@ -1,2849 +1,2836 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "notecontent.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include //For m_simpleRichText->documentLayout() #include //For QPixmap::createHeuristicMask() #include #include #include #include #include #include #include #include #include #include #include //For KIO::file_preview(...) #include #include #include #include #include "basketscene.h" #include "config.h" #include "debugwindow.h" #include "file_metadata.h" #include "filter.h" #include "global.h" #include "htmlexporter.h" #include "note.h" #include "notefactory.h" #include "settings.h" #include "tools.h" #include "xmlwork.h" /** * LinkDisplayItem definition * */ QRectF LinkDisplayItem::boundingRect() const { if (m_note) { return QRect(0, 0, m_note->width() - m_note->contentX() - Note::NOTE_MARGIN, m_note->height() - 2 * Note::NOTE_MARGIN); } return QRectF(); } void LinkDisplayItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { if (!m_note) return; QRectF rect = boundingRect(); m_linkDisplay.paint(painter, 0, 0, rect.width(), rect.height(), m_note->palette(), true, m_note->isSelected(), m_note->hovered(), m_note->hovered() && m_note->hoveredZone() == Note::Custom0); } /** class NoteContent: */ const int NoteContent::FEEDBACK_DARKING = 105; NoteContent::NoteContent(Note *parent, const QString &fileName) : m_note(parent) { parent->setContent(this); setFileName(fileName); } void NoteContent::saveToNode(QXmlStreamWriter &stream) { if (useFile()) { stream.writeStartElement("content"); stream.writeCharacters(fileName()); stream.writeEndElement(); } } QRectF NoteContent::zoneRect(int zone, const QPointF & /*pos*/) { if (zone == Note::Content) return QRectF(0, 0, note()->width(), note()->height()); // Too wide and height, but it will be clipped by Note::zoneRect() else return QRectF(); } QUrl NoteContent::urlToOpen(bool /*with*/) { return (useFile() ? QUrl::fromLocalFile(fullPath()) : QUrl()); } void NoteContent::setFileName(const QString &fileName) { m_fileName = fileName; } bool NoteContent::trySetFileName(const QString &fileName) { if (useFile() && fileName != m_fileName) { QString newFileName = Tools::fileNameForNewFile(fileName, basket()->fullPath()); QDir dir; dir.rename(fullPath(), basket()->fullPathForFileName(newFileName)); return true; } return false; // !useFile() or unsuccessful rename } QString NoteContent::fullPath() { if (note() && useFile()) return note()->fullPath(); else return ""; } void NoteContent::contentChanged(qreal newMinWidth) { m_minWidth = newMinWidth; if (note()) { // note()->unbufferize(); note()->requestRelayout(); // TODO: It should re-set the width! m_width = 0 ? contentChanged: setWidth, geteight, if size havent changed, only repaint and not relayout } } BasketScene *NoteContent::basket() { if (note()) return note()->basket(); else return 0; } void NoteContent::setEdited() { note()->setLastModificationDate(QDateTime::currentDateTime()); basket()->save(); } /** All the Content Classes: */ NoteType::Id TextContent::type() const { return NoteType::Text; } NoteType::Id HtmlContent::type() const { return NoteType::Html; } NoteType::Id ImageContent::type() const { return NoteType::Image; } NoteType::Id AnimationContent::type() const { return NoteType::Animation; } NoteType::Id SoundContent::type() const { return NoteType::Sound; } NoteType::Id FileContent::type() const { return NoteType::File; } NoteType::Id LinkContent::type() const { return NoteType::Link; } NoteType::Id CrossReferenceContent::type() const { return NoteType::CrossReference; } NoteType::Id LauncherContent::type() const { return NoteType::Launcher; } NoteType::Id ColorContent::type() const { return NoteType::Color; } NoteType::Id UnknownContent::type() const { return NoteType::Unknown; } QString TextContent::typeName() const { return i18n("Plain Text"); } QString HtmlContent::typeName() const { return i18n("Text"); } QString ImageContent::typeName() const { return i18n("Image"); } QString AnimationContent::typeName() const { return i18n("Animation"); } QString SoundContent::typeName() const { return i18n("Sound"); } QString FileContent::typeName() const { return i18n("File"); } QString LinkContent::typeName() const { return i18n("Link"); } QString CrossReferenceContent::typeName() const { return i18n("Cross Reference"); } QString LauncherContent::typeName() const { return i18n("Launcher"); } QString ColorContent::typeName() const { return i18n("Color"); } QString UnknownContent::typeName() const { return i18n("Unknown"); } QString TextContent::lowerTypeName() const { return "text"; } QString HtmlContent::lowerTypeName() const { return "html"; } QString ImageContent::lowerTypeName() const { return "image"; } QString AnimationContent::lowerTypeName() const { return "animation"; } QString SoundContent::lowerTypeName() const { return "sound"; } QString FileContent::lowerTypeName() const { return "file"; } QString LinkContent::lowerTypeName() const { return "link"; } QString CrossReferenceContent::lowerTypeName() const { return "cross_reference"; } QString LauncherContent::lowerTypeName() const { return "launcher"; } QString ColorContent::lowerTypeName() const { return "color"; } QString UnknownContent::lowerTypeName() const { return "unknown"; } QString NoteContent::toText(const QString &cuttedFullPath) { return (cuttedFullPath.isEmpty() ? fullPath() : cuttedFullPath); } QString TextContent::toText(const QString & /*cuttedFullPath*/) { return text(); } QString HtmlContent::toText(const QString & /*cuttedFullPath*/) { return Tools::htmlToText(html()); } QString LinkContent::toText(const QString & /*cuttedFullPath*/) { if (autoTitle()) return url().toDisplayString(); else if (title().isEmpty() && url().isEmpty()) return ""; else if (url().isEmpty()) return title(); else if (title().isEmpty()) return url().toDisplayString(); else return QString("%1 <%2>").arg(title(), url().toDisplayString()); } QString CrossReferenceContent::toText(const QString & /*cuttedFullPath*/) { if (title().isEmpty() && url().isEmpty()) return ""; else if (url().isEmpty()) return title(); else if (title().isEmpty()) return url().toDisplayString(); else return QString("%1 <%2>").arg(title(), url().toDisplayString()); } QString ColorContent::toText(const QString & /*cuttedFullPath*/) { return color().name(); } QString UnknownContent::toText(const QString & /*cuttedFullPath*/) { return ""; } // TODO: If imageName.isEmpty() return fullPath() because it's for external use, else return fileName() because it's to display in a tooltip QString TextContent::toHtml(const QString & /*imageName*/, const QString & /*cuttedFullPath*/) { return Tools::textToHTMLWithoutP(text()); } QString HtmlContent::toHtml(const QString & /*imageName*/, const QString & /*cuttedFullPath*/) { // return Tools::htmlToParagraph(html()); QTextDocument simpleRichText; simpleRichText.setHtml(html()); return Tools::textDocumentToMinimalHTML(&simpleRichText); } QString ImageContent::toHtml(const QString & /*imageName*/, const QString &cuttedFullPath) { return QString("").arg(cuttedFullPath.isEmpty() ? fullPath() : cuttedFullPath); } QString AnimationContent::toHtml(const QString & /*imageName*/, const QString &cuttedFullPath) { return QString("").arg(cuttedFullPath.isEmpty() ? fullPath() : cuttedFullPath); } QString SoundContent::toHtml(const QString & /*imageName*/, const QString &cuttedFullPath) { return QString("%2").arg((cuttedFullPath.isEmpty() ? fullPath() : cuttedFullPath), fileName()); } // With the icon? QString FileContent::toHtml(const QString & /*imageName*/, const QString &cuttedFullPath) { return QString("%2").arg((cuttedFullPath.isEmpty() ? fullPath() : cuttedFullPath), fileName()); } // With the icon? QString LinkContent::toHtml(const QString & /*imageName*/, const QString & /*cuttedFullPath*/) { return QString("%2").arg(url().toDisplayString(), title()); } // With the icon? QString CrossReferenceContent::toHtml(const QString & /*imageName*/, const QString & /*cuttedFullPath*/) { return QString("%2").arg(url().toDisplayString(), title()); } // With the icon? QString LauncherContent::toHtml(const QString & /*imageName*/, const QString &cuttedFullPath) { return QString("%2").arg((cuttedFullPath.isEmpty() ? fullPath() : cuttedFullPath), name()); } // With the icon? QString ColorContent::toHtml(const QString & /*imageName*/, const QString & /*cuttedFullPath*/) { return QString("%2").arg(color().name(), color().name()); } QString UnknownContent::toHtml(const QString & /*imageName*/, const QString & /*cuttedFullPath*/) { return ""; } QPixmap ImageContent::toPixmap() { return pixmap(); } QPixmap AnimationContent::toPixmap() { return m_movie->currentPixmap(); } void NoteContent::toLink(QUrl *url, QString *title, const QString &cuttedFullPath) { if (useFile()) { *url = QUrl::fromUserInput(cuttedFullPath.isEmpty() ? fullPath() : cuttedFullPath); *title = (cuttedFullPath.isEmpty() ? fullPath() : cuttedFullPath); } else { *url = QUrl(); title->clear(); } } void LinkContent::toLink(QUrl *url, QString *title, const QString & /*cuttedFullPath*/) { *url = this->url(); *title = this->title(); } void CrossReferenceContent::toLink(QUrl *url, QString *title, const QString & /*cuttedFullPath*/) { *url = this->url(); *title = this->title(); } void LauncherContent::toLink(QUrl *url, QString *title, const QString &cuttedFullPath) { *url = QUrl::fromUserInput(cuttedFullPath.isEmpty() ? fullPath() : cuttedFullPath); *title = name(); } void UnknownContent::toLink(QUrl *url, QString *title, const QString & /*cuttedFullPath*/) { *url = QUrl(); *title = QString(); } bool TextContent::useFile() const { return true; } bool HtmlContent::useFile() const { return true; } bool ImageContent::useFile() const { return true; } bool AnimationContent::useFile() const { return true; } bool SoundContent::useFile() const { return true; } bool FileContent::useFile() const { return true; } bool LinkContent::useFile() const { return false; } bool CrossReferenceContent::useFile() const { return false; } bool LauncherContent::useFile() const { return true; } bool ColorContent::useFile() const { return false; } bool UnknownContent::useFile() const { return true; } bool TextContent::canBeSavedAs() const { return true; } bool HtmlContent::canBeSavedAs() const { return true; } bool ImageContent::canBeSavedAs() const { return true; } bool AnimationContent::canBeSavedAs() const { return true; } bool SoundContent::canBeSavedAs() const { return true; } bool FileContent::canBeSavedAs() const { return true; } bool LinkContent::canBeSavedAs() const { return true; } bool CrossReferenceContent::canBeSavedAs() const { return true; } bool LauncherContent::canBeSavedAs() const { return true; } bool ColorContent::canBeSavedAs() const { return false; } bool UnknownContent::canBeSavedAs() const { return false; } QString TextContent::saveAsFilters() const { return "text/plain"; } QString HtmlContent::saveAsFilters() const { return "text/html"; } QString ImageContent::saveAsFilters() const { return "image/png"; } // TODO: Offer more types QString AnimationContent::saveAsFilters() const { return "image/gif"; } // TODO: MNG... QString SoundContent::saveAsFilters() const { return "audio/mp3 audio/ogg"; } // TODO: OGG... QString FileContent::saveAsFilters() const { return "*"; } // TODO: Get MIME type of the url target QString LinkContent::saveAsFilters() const { return "*"; } // TODO: idem File + If isDir() const: return QString CrossReferenceContent::saveAsFilters() const { return "*"; } // TODO: idem File + If isDir() const: return QString LauncherContent::saveAsFilters() const { return "application/x-desktop"; } QString ColorContent::saveAsFilters() const { return ""; } QString UnknownContent::saveAsFilters() const { return ""; } bool TextContent::match(const FilterData &data) { return text().contains(data.string); } bool HtmlContent::match(const FilterData &data) { return m_textEquivalent /*toText("")*/.contains(data.string); } // OPTIM_FILTER bool ImageContent::match(const FilterData & /*data*/) { return false; } bool AnimationContent::match(const FilterData & /*data*/) { return false; } bool SoundContent::match(const FilterData &data) { return fileName().contains(data.string); } bool FileContent::match(const FilterData &data) { return fileName().contains(data.string); } bool LinkContent::match(const FilterData &data) { return title().contains(data.string) || url().toDisplayString().contains(data.string); } bool CrossReferenceContent::match(const FilterData &data) { return title().contains(data.string) || url().toDisplayString().contains(data.string); } bool LauncherContent::match(const FilterData &data) { return exec().contains(data.string) || name().contains(data.string); } bool ColorContent::match(const FilterData &data) { return color().name().contains(data.string); } bool UnknownContent::match(const FilterData &data) { return mimeTypes().contains(data.string); } QString TextContent::editToolTipText() const { return i18n("Edit this plain text"); } QString HtmlContent::editToolTipText() const { return i18n("Edit this text"); } QString ImageContent::editToolTipText() const { return i18n("Edit this image"); } QString AnimationContent::editToolTipText() const { return i18n("Edit this animation"); } QString SoundContent::editToolTipText() const { return i18n("Edit the file name of this sound"); } QString FileContent::editToolTipText() const { return i18n("Edit the name of this file"); } QString LinkContent::editToolTipText() const { return i18n("Edit this link"); } QString CrossReferenceContent::editToolTipText() const { return i18n("Edit this cross reference"); } QString LauncherContent::editToolTipText() const { return i18n("Edit this launcher"); } QString ColorContent::editToolTipText() const { return i18n("Edit this color"); } QString UnknownContent::editToolTipText() const { return i18n("Edit this unknown object"); } QString TextContent::cssClass() const { return ""; } QString HtmlContent::cssClass() const { return ""; } QString ImageContent::cssClass() const { return ""; } QString AnimationContent::cssClass() const { return ""; } QString SoundContent::cssClass() const { return "sound"; } QString FileContent::cssClass() const { return "file"; } QString LinkContent::cssClass() const { return (LinkLook::lookForURL(m_url) == LinkLook::localLinkLook ? "local" : "network"); } QString CrossReferenceContent::cssClass() const { return "cross_reference"; } QString LauncherContent::cssClass() const { return "launcher"; } QString ColorContent::cssClass() const { return ""; } QString UnknownContent::cssClass() const { return ""; } void TextContent::fontChanged() { setText(text()); } void HtmlContent::fontChanged() { QTextDocument *richDoc = m_graphicsTextItem.document(); // This check is important when applying style to a note which is not loaded yet. Example: // Filter all -> open some basket for the first time -> close filter: if a note was tagged as TODO, then it would display no text if (!richDoc->isEmpty()) setHtml(Tools::textDocumentToMinimalHTML(richDoc)); } void ImageContent::fontChanged() { setPixmap(pixmap()); } void AnimationContent::fontChanged() { /*startMovie();*/ } void FileContent::fontChanged() { setFileName(fileName()); } void LinkContent::fontChanged() { setLink(url(), title(), icon(), autoTitle(), autoIcon()); } void CrossReferenceContent::fontChanged() { setCrossReference(url(), title(), icon()); } void LauncherContent::fontChanged() { setLauncher(name(), icon(), exec()); } void ColorContent::fontChanged() { setColor(color()); } void UnknownContent::fontChanged() { loadFromFile(/*lazyLoad=*/false); } // TODO: Optimize: setMimeTypes() // QString TextContent::customOpenCommand() { return (Settings::isTextUseProg() && ! Settings::textProg().isEmpty() ? Settings::textProg() : QString()); } QString HtmlContent::customOpenCommand() { return (Settings::isHtmlUseProg() && !Settings::htmlProg().isEmpty() ? Settings::htmlProg() : QString()); } QString ImageContent::customOpenCommand() { return (Settings::isImageUseProg() && !Settings::imageProg().isEmpty() ? Settings::imageProg() : QString()); } QString AnimationContent::customOpenCommand() { return (Settings::isAnimationUseProg() && !Settings::animationProg().isEmpty() ? Settings::animationProg() : QString()); } QString SoundContent::customOpenCommand() { return (Settings::isSoundUseProg() && !Settings::soundProg().isEmpty() ? Settings::soundProg() : QString()); } void LinkContent::serialize(QDataStream &stream) { stream << url() << title() << icon() << (quint64)autoTitle() << (quint64)autoIcon(); } void CrossReferenceContent::serialize(QDataStream &stream) { stream << url() << title() << icon(); } void ColorContent::serialize(QDataStream &stream) { stream << color(); } QPixmap TextContent::feedbackPixmap(qreal width, qreal height) { QRectF textRect = QFontMetrics(note()->font()).boundingRect(0, 0, width, height, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, text()); QPixmap pixmap(qMin(width, textRect.width()), qMin(height, textRect.height())); pixmap.fill(note()->backgroundColor().dark(FEEDBACK_DARKING)); QPainter painter(&pixmap); painter.setPen(note()->textColor()); painter.setFont(note()->font()); painter.drawText(0, 0, pixmap.width(), pixmap.height(), Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, text()); painter.end(); return pixmap; } QPixmap HtmlContent::feedbackPixmap(qreal width, qreal height) { QTextDocument richText; richText.setHtml(html()); richText.setDefaultFont(note()->font()); richText.setTextWidth(width); QPalette palette; palette = basket()->palette(); palette.setColor(QPalette::Text, note()->textColor()); palette.setColor(QPalette::Background, note()->backgroundColor().dark(FEEDBACK_DARKING)); QPixmap pixmap(qMin(width, richText.idealWidth()), qMin(height, richText.size().height())); pixmap.fill(note()->backgroundColor().dark(FEEDBACK_DARKING)); QPainter painter(&pixmap); painter.setPen(note()->textColor()); painter.translate(0, 0); richText.drawContents(&painter, QRectF(0, 0, pixmap.width(), pixmap.height())); painter.end(); return pixmap; } QPixmap ImageContent::feedbackPixmap(qreal width, qreal height) { if (width >= m_pixmapItem.pixmap().width() && height >= m_pixmapItem.pixmap().height()) { // Full size if (m_pixmapItem.pixmap().hasAlpha()) { QPixmap opaque(m_pixmapItem.pixmap().width(), m_pixmapItem.pixmap().height()); opaque.fill(note()->backgroundColor().dark(FEEDBACK_DARKING)); QPainter painter(&opaque); painter.drawPixmap(0, 0, m_pixmapItem.pixmap()); painter.end(); return opaque; } else { return m_pixmapItem.pixmap(); } } else { // Scaled down QImage imageToScale = m_pixmapItem.pixmap().toImage(); QPixmap pmScaled; pmScaled = QPixmap::fromImage(imageToScale.scaled(width, height, Qt::KeepAspectRatio)); if (pmScaled.hasAlpha()) { QPixmap opaque(pmScaled.width(), pmScaled.height()); opaque.fill(note()->backgroundColor().dark(FEEDBACK_DARKING)); QPainter painter(&opaque); painter.drawPixmap(0, 0, pmScaled); painter.end(); return opaque; } else { return pmScaled; } } } QPixmap AnimationContent::feedbackPixmap(qreal width, qreal height) { QPixmap pixmap = m_movie->currentPixmap(); if (width >= pixmap.width() && height >= pixmap.height()) // Full size return pixmap; else { // Scaled down QImage imageToScale = pixmap.toImage(); QPixmap pmScaled; pmScaled = QPixmap::fromImage(imageToScale.scaled(width, height, Qt::KeepAspectRatio)); return pmScaled; } } QPixmap LinkContent::feedbackPixmap(qreal width, qreal height) { QPalette palette; palette = basket()->palette(); palette.setColor(QPalette::WindowText, note()->textColor()); palette.setColor(QPalette::Background, note()->backgroundColor().dark(FEEDBACK_DARKING)); return m_linkDisplayItem.linkDisplay().feedbackPixmap(width, height, palette, /*isDefaultColor=*/note()->textColor() == basket()->textColor()); } QPixmap CrossReferenceContent::feedbackPixmap(qreal width, qreal height) { QPalette palette; palette = basket()->palette(); palette.setColor(QPalette::WindowText, note()->textColor()); palette.setColor(QPalette::Background, note()->backgroundColor().dark(FEEDBACK_DARKING)); return m_linkDisplayItem.linkDisplay().feedbackPixmap(width, height, palette, /*isDefaultColor=*/note()->textColor() == basket()->textColor()); } QPixmap ColorContent::feedbackPixmap(qreal width, qreal height) { // TODO: Duplicate code: make a rect() method! QRectF boundingRect = m_colorItem.boundingRect(); QPalette palette; palette = basket()->palette(); palette.setColor(QPalette::WindowText, note()->textColor()); palette.setColor(QPalette::Background, note()->backgroundColor().dark(FEEDBACK_DARKING)); QPixmap pixmap(qMin(width, boundingRect.width()), qMin(height, boundingRect.height())); pixmap.fill(note()->backgroundColor().dark(FEEDBACK_DARKING)); QPainter painter(&pixmap); m_colorItem.paint(&painter, 0, 0); //, pixmap.width(), pixmap.height(), palette, false, false, false); // We don't care of the three last boolean parameters. painter.end(); return pixmap; } QPixmap FileContent::feedbackPixmap(qreal width, qreal height) { QPalette palette; palette = basket()->palette(); palette.setColor(QPalette::WindowText, note()->textColor()); palette.setColor(QPalette::Background, note()->backgroundColor().dark(FEEDBACK_DARKING)); return m_linkDisplayItem.linkDisplay().feedbackPixmap(width, height, palette, /*isDefaultColor=*/note()->textColor() == basket()->textColor()); } QPixmap LauncherContent::feedbackPixmap(qreal width, qreal height) { QPalette palette; palette = basket()->palette(); palette.setColor(QPalette::WindowText, note()->textColor()); palette.setColor(QPalette::Background, note()->backgroundColor().dark(FEEDBACK_DARKING)); return m_linkDisplayItem.linkDisplay().feedbackPixmap(width, height, palette, /*isDefaultColor=*/note()->textColor() == basket()->textColor()); } QPixmap UnknownContent::feedbackPixmap(qreal width, qreal height) { QRectF boundingRect = m_unknownItem.boundingRect(); QPalette palette; palette = basket()->palette(); palette.setColor(QPalette::WindowText, note()->textColor()); palette.setColor(QPalette::Background, note()->backgroundColor().dark(FEEDBACK_DARKING)); QPixmap pixmap(qMin(width, boundingRect.width()), qMin(height, boundingRect.height())); QPainter painter(&pixmap); m_unknownItem.paint(&painter, 0, 0); //, pixmap.width() + 1, pixmap.height(), palette, false, false, false); // We don't care of the three last boolean parameters. painter.setPen(note()->backgroundColor().dark(FEEDBACK_DARKING)); painter.drawPoint(0, 0); painter.drawPoint(pixmap.width() - 1, 0); painter.drawPoint(0, pixmap.height() - 1); painter.drawPoint(pixmap.width() - 1, pixmap.height() - 1); painter.end(); return pixmap; } /** class TextContent: */ TextContent::TextContent(Note *parent, const QString &fileName, bool lazyLoad) : NoteContent(parent, fileName) , m_graphicsTextItem(parent) { if (parent) { parent->addToGroup(&m_graphicsTextItem); m_graphicsTextItem.setPos(parent->contentX(), Note::NOTE_MARGIN); } basket()->addWatchedFile(fullPath()); loadFromFile(lazyLoad); } TextContent::~TextContent() { if (note()) note()->removeFromGroup(&m_graphicsTextItem); } qreal TextContent::setWidthAndGetHeight(qreal /*width*/) { return m_graphicsTextItem.boundingRect().height(); } bool TextContent::loadFromFile(bool lazyLoad) { DEBUG_WIN << "Loading TextContent From " + basket()->folderName() + fileName(); QString content; bool success = basket()->loadFromFile(fullPath(), &content); if (success) setText(content, lazyLoad); else { qDebug() << "FAILED TO LOAD TextContent: " << fullPath(); setText("", lazyLoad); if (!QFile::exists(fullPath())) saveToFile(); // Reserve the fileName so no new note will have the same name! } return success; } bool TextContent::finishLazyLoad() { m_graphicsTextItem.setFont(note()->font()); contentChanged(m_graphicsTextItem.boundingRect().width() + 1); return true; } bool TextContent::saveToFile() { return basket()->saveToFile(fullPath(), text()); } QString TextContent::linkAt(const QPointF & /*pos*/) { return ""; /* if (m_simpleRichText) return m_simpleRichText->documentLayout()->anchorAt(pos); else return ""; // Lazy loaded*/ } QString TextContent::messageWhenOpening(OpenMessage where) { switch (where) { case OpenOne: return i18n("Opening plain text..."); case OpenSeveral: return i18n("Opening plain texts..."); case OpenOneWith: return i18n("Opening plain text with..."); case OpenSeveralWith: return i18n("Opening plain texts with..."); case OpenOneWithDialog: return i18n("Open plain text with:"); case OpenSeveralWithDialog: return i18n("Open plain texts with:"); default: return ""; } } void TextContent::setText(const QString &text, bool lazyLoad) { m_graphicsTextItem.setText(text); if (!lazyLoad) finishLazyLoad(); else contentChanged(m_graphicsTextItem.boundingRect().width()); } void TextContent::exportToHTML(HTMLExporter *exporter, int indent) { QString spaces; QString html = "" + Tools::tagCrossReferences(Tools::tagURLs(Tools::textToHTMLWithoutP(text().replace(QChar('\t'), " "))), false, exporter); // Don't collapse multiple spaces! exporter->stream << html.replace(" ", "  ").replace(QChar('\n'), '\n' + spaces.fill(' ', indent + 1)); } /** class HtmlContent: */ HtmlContent::HtmlContent(Note *parent, const QString &fileName, bool lazyLoad) : NoteContent(parent, fileName) , m_simpleRichText(0) , m_graphicsTextItem(parent) { if (parent) { parent->addToGroup(&m_graphicsTextItem); m_graphicsTextItem.setPos(parent->contentX(), Note::NOTE_MARGIN); } basket()->addWatchedFile(fullPath()); loadFromFile(lazyLoad); } HtmlContent::~HtmlContent() { if (note()) note()->removeFromGroup(&m_graphicsTextItem); delete m_simpleRichText; } qreal HtmlContent::setWidthAndGetHeight(qreal width) { width -= 1; m_graphicsTextItem.setTextWidth(width); return m_graphicsTextItem.boundingRect().height(); } bool HtmlContent::loadFromFile(bool lazyLoad) { DEBUG_WIN << "Loading HtmlContent From " + basket()->folderName() + fileName(); QString content; bool success = basket()->loadFromFile(fullPath(), &content); if (success) setHtml(content, lazyLoad); else { setHtml("", lazyLoad); if (!QFile::exists(fullPath())) saveToFile(); // Reserve the fileName so no new note will have the same name! } return success; } bool HtmlContent::finishLazyLoad() { qreal width = m_graphicsTextItem.document()->idealWidth(); m_graphicsTextItem.setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable); m_graphicsTextItem.setTextInteractionFlags(Qt::TextEditorInteraction); /*QString css = ".cross_reference { display: block; width: 100%; text-decoration: none; color: #336600; }" "a:hover.cross_reference { text-decoration: underline; color: #ff8000; }"; m_graphicsTextItem.document()->setDefaultStyleSheet(css);*/ QString convert = Tools::tagURLs(m_html); if (note()->allowCrossReferences()) convert = Tools::tagCrossReferences(convert); m_graphicsTextItem.setHtml(convert); m_graphicsTextItem.setDefaultTextColor(note()->textColor()); m_graphicsTextItem.setFont(note()->font()); m_graphicsTextItem.setTextWidth(1); // We put a width of 1 pixel, so usedWidth() is equal to the minimum width int minWidth = m_graphicsTextItem.document()->idealWidth(); m_graphicsTextItem.setTextWidth(width); contentChanged(minWidth + 1); return true; } bool HtmlContent::saveToFile() { return basket()->saveToFile(fullPath(), html()); } QString HtmlContent::linkAt(const QPointF &pos) { return m_graphicsTextItem.document()->documentLayout()->anchorAt(pos); } QString HtmlContent::messageWhenOpening(OpenMessage where) { switch (where) { case OpenOne: return i18n("Opening text..."); case OpenSeveral: return i18n("Opening texts..."); case OpenOneWith: return i18n("Opening text with..."); case OpenSeveralWith: return i18n("Opening texts with..."); case OpenOneWithDialog: return i18n("Open text with:"); case OpenSeveralWithDialog: return i18n("Open texts with:"); default: return ""; } } void HtmlContent::setHtml(const QString &html, bool lazyLoad) { m_html = html; /* The code was commented, so now non-Latin text is stored directly in Unicode. * If testing doesn't show any bugs, this block should be deleted QRegExp rx("([^\\x00-\\x7f])"); while (m_html.contains(rx)) { m_html.replace( rx.cap().unicode()[0], QString("&#%1;").arg(rx.cap().unicode()[0].unicode()) ); }*/ m_textEquivalent = toText(""); // OPTIM_FILTER if (!lazyLoad) finishLazyLoad(); else contentChanged(10); } void HtmlContent::exportToHTML(HTMLExporter *exporter, int indent) { QString spaces; QString convert = Tools::tagURLs(html().replace("\t", " ")); if (note()->allowCrossReferences()) convert = Tools::tagCrossReferences(convert, false, exporter); exporter->stream << Tools::htmlToParagraph(convert).replace(" ", "  ").replace("\n", '\n' + spaces.fill(' ', indent + 1)); } /** class ImageContent: */ ImageContent::ImageContent(Note *parent, const QString &fileName, bool lazyLoad) : NoteContent(parent, fileName) , m_pixmapItem(parent) , m_format() { if (parent) { parent->addToGroup(&m_pixmapItem); m_pixmapItem.setPos(parent->contentX(), Note::NOTE_MARGIN); } basket()->addWatchedFile(fullPath()); loadFromFile(lazyLoad); } ImageContent::~ImageContent() { if (note()) note()->removeFromGroup(&m_pixmapItem); } qreal ImageContent::setWidthAndGetHeight(qreal width) { width -= 1; // Don't store width: we will get it on paint! if (width >= m_pixmapItem.pixmap().width()) // Full size { m_pixmapItem.setScale(1.0); return m_pixmapItem.boundingRect().height(); } else { // Scaled down qreal scaleFactor = width / m_pixmapItem.pixmap().width(); m_pixmapItem.setScale(scaleFactor); return m_pixmapItem.boundingRect().height() * scaleFactor; } } bool ImageContent::loadFromFile(bool lazyLoad) { if (lazyLoad) return true; else return finishLazyLoad(); } bool ImageContent::finishLazyLoad() { DEBUG_WIN << "Loading ImageContent From " + basket()->folderName() + fileName(); QByteArray content; QPixmap pixmap; if (basket()->loadFromFile(fullPath(), &content)) { QBuffer buffer(&content); buffer.open(QIODevice::ReadOnly); m_format = QImageReader::imageFormat(&buffer); // See QImageIO to know what formats can be supported. buffer.close(); if (!m_format.isNull()) { pixmap.loadFromData(content); setPixmap(pixmap); return true; } } qDebug() << "FAILED TO LOAD ImageContent: " << fullPath(); m_format = "PNG"; // If the image is set later, it should be saved without destruction, so we use PNG by default. pixmap = QPixmap(1, 1); // Create a 1x1 pixels image instead of an undefined one. pixmap.fill(); pixmap.setMask(pixmap.createHeuristicMask()); setPixmap(pixmap); if (!QFile::exists(fullPath())) saveToFile(); // Reserve the fileName so no new note will have the same name! return false; } bool ImageContent::saveToFile() { QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); m_pixmapItem.pixmap().save(&buffer, m_format); return basket()->saveToFile(fullPath(), ba); } void ImageContent::toolTipInfos(QStringList *keys, QStringList *values) { keys->append(i18n("Size")); values->append(i18n("%1 by %2 pixels", QString::number(m_pixmapItem.pixmap().width()), QString::number(m_pixmapItem.pixmap().height()))); } QString ImageContent::messageWhenOpening(OpenMessage where) { switch (where) { case OpenOne: return i18n("Opening image..."); case OpenSeveral: return i18n("Opening images..."); case OpenOneWith: return i18n("Opening image with..."); case OpenSeveralWith: return i18n("Opening images with..."); case OpenOneWithDialog: return i18n("Open image with:"); case OpenSeveralWithDialog: return i18n("Open images with:"); default: return ""; } } void ImageContent::setPixmap(const QPixmap &pixmap) { m_pixmapItem.setPixmap(pixmap); // Since it's scaled, the height is always greater or equal to the size of the tag emblems (16) contentChanged(16 + 1); // TODO: always good? I don't think... } void ImageContent::exportToHTML(HTMLExporter *exporter, int /*indent*/) { qreal width = m_pixmapItem.pixmap().width(); qreal height = m_pixmapItem.pixmap().height(); qreal contentWidth = note()->width() - note()->contentX() - 1 - Note::NOTE_MARGIN; QString imageName = exporter->copyFile(fullPath(), /*createIt=*/true); if (contentWidth <= m_pixmapItem.pixmap().width()) { // Scaled down qreal scale = contentWidth / m_pixmapItem.pixmap().width(); width = m_pixmapItem.pixmap().width() * scale; height = m_pixmapItem.pixmap().height() * scale; exporter->stream << "dataFolderName << imageName << "\" title=\"" << i18n("Click for full size view") << "\">"; } exporter->stream << "dataFolderName << imageName << "\" width=\"" << width << "\" height=\"" << height << "\" alt=\"\">"; if (contentWidth <= m_pixmapItem.pixmap().width()) // Scaled down exporter->stream << ""; } /** class AnimationContent: */ AnimationContent::AnimationContent(Note *parent, const QString &fileName, bool lazyLoad) : NoteContent(parent, fileName) , m_buffer(new QBuffer(this)) , m_movie(new QMovie(this)) , m_currentWidth(0) , m_graphicsPixmap(parent) { if (parent) { parent->addToGroup(&m_graphicsPixmap); m_graphicsPixmap.setPos(parent->contentX(), Note::NOTE_MARGIN); connect(parent->basket(), SIGNAL(activated()), m_movie, SLOT(start())); connect(parent->basket(), SIGNAL(closed()), m_movie, SLOT(stop())); } basket()->addWatchedFile(fullPath()); connect(m_movie, SIGNAL(resized(QSize)), this, SLOT(movieResized())); connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(movieFrameChanged())); loadFromFile(lazyLoad); } AnimationContent::~AnimationContent() { note()->removeFromGroup(&m_graphicsPixmap); } qreal AnimationContent::setWidthAndGetHeight(qreal width) { m_currentWidth = width; QPixmap pixmap = m_graphicsPixmap.pixmap(); if (pixmap.width() > m_currentWidth) { qreal scaleFactor = m_currentWidth / pixmap.width(); m_graphicsPixmap.setScale(scaleFactor); return pixmap.height() * scaleFactor; } else { m_graphicsPixmap.setScale(1.0); return pixmap.height(); } return 0; } bool AnimationContent::loadFromFile(bool lazyLoad) { if (lazyLoad) return true; else return finishLazyLoad(); } bool AnimationContent::finishLazyLoad() { QByteArray content; if (basket()->loadFromFile(fullPath(), &content)) { m_buffer->setData(content); startMovie(); contentChanged(16); return true; } m_buffer->setData(0); return false; } bool AnimationContent::saveToFile() { // Impossible! return false; } QString AnimationContent::messageWhenOpening(OpenMessage where) { switch (where) { case OpenOne: return i18n("Opening animation..."); case OpenSeveral: return i18n("Opening animations..."); case OpenOneWith: return i18n("Opening animation with..."); case OpenSeveralWith: return i18n("Opening animations with..."); case OpenOneWithDialog: return i18n("Open animation with:"); case OpenSeveralWithDialog: return i18n("Open animations with:"); default: return ""; } } bool AnimationContent::startMovie() { if (m_buffer->data().isEmpty()) return false; m_movie->setDevice(m_buffer); m_movie->start(); return true; } void AnimationContent::movieUpdated() { m_graphicsPixmap.setPixmap(m_movie->currentPixmap()); } void AnimationContent::movieResized() { m_graphicsPixmap.setPixmap(m_movie->currentPixmap()); } void AnimationContent::movieFrameChanged() { m_graphicsPixmap.setPixmap(m_movie->currentPixmap()); } void AnimationContent::exportToHTML(HTMLExporter *exporter, int /*indent*/) { exporter->stream << QString("\"\"") .arg(exporter->dataFolderName + exporter->copyFile(fullPath(), /*createIt=*/true), QString::number(m_movie->currentPixmap().size().width()), QString::number(m_movie->currentPixmap().size().height())); } /** class FileContent: */ FileContent::FileContent(Note *parent, const QString &fileName) : NoteContent(parent, fileName) , m_linkDisplayItem(parent) , m_previewJob(0) { basket()->addWatchedFile(fullPath()); setFileName(fileName); // FIXME: TO THAT HERE BECAUSE NoteContent() constructor seems to don't be able to call virtual methods??? if (parent) { parent->addToGroup(&m_linkDisplayItem); m_linkDisplayItem.setPos(parent->contentX(), Note::NOTE_MARGIN); } } FileContent::~FileContent() { if (note()) note()->removeFromGroup(&m_linkDisplayItem); } qreal FileContent::setWidthAndGetHeight(qreal width) { m_linkDisplayItem.linkDisplay().setWidth(width); return m_linkDisplayItem.linkDisplay().height(); } bool FileContent::loadFromFile(bool /*lazyLoad*/) { setFileName(fileName()); // File changed: get new file preview! return true; } void FileContent::toolTipInfos(QStringList *keys, QStringList *values) { // Get the size of the file: uint size = QFileInfo(fullPath()).size(); QString humanFileSize = KIO::convertSize((KIO::filesize_t)size); keys->append(i18n("Size")); values->append(humanFileSize); QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(QUrl::fromLocalFile(fullPath())); if (mime.isValid()) { keys->append(i18n("Type")); values->append(mime.comment()); } MetaDataExtractionResult result(fullPath(), mime.name()); KFileMetaData::ExtractorCollection extractorCollection; for (KFileMetaData::Extractor *ex : extractorCollection.fetchExtractors(mime.name())) { ex->extract(&result); auto groups = result.preferredGroups(); DEBUG_WIN << "Metadata Extractor result has " << QString::number(groups.count()) << " groups"; int i = 0; for (auto it = groups.begin(); i < 6 && it != groups.end(); ++it) { if (!it->second.isEmpty()) { keys->append(it->first); values->append(it->second); i++; } } } } int FileContent::zoneAt(const QPointF &pos) { return (m_linkDisplayItem.linkDisplay().iconButtonAt(pos) ? 0 : Note::Custom0); } QRectF FileContent::zoneRect(int zone, const QPointF & /*pos*/) { QRectF linkRect = m_linkDisplayItem.linkDisplay().iconButtonRect(); if (zone == Note::Custom0) return QRectF(linkRect.width(), 0, note()->width(), note()->height()); // Too wide and height, but it will be clipped by Note::zoneRect() else if (zone == Note::Content) return linkRect; else return QRectF(); } QString FileContent::zoneTip(int zone) { return (zone == Note::Custom0 ? i18n("Open this file") : QString()); } Qt::CursorShape FileContent::cursorFromZone(int zone) const { if (zone == Note::Custom0) return Qt::PointingHandCursor; return Qt::ArrowCursor; } int FileContent::xEditorIndent() { return m_linkDisplayItem.linkDisplay().iconButtonRect().width() + 2; } QString FileContent::messageWhenOpening(OpenMessage where) { switch (where) { case OpenOne: return i18n("Opening file..."); case OpenSeveral: return i18n("Opening files..."); case OpenOneWith: return i18n("Opening file with..."); case OpenSeveralWith: return i18n("Opening files with..."); case OpenOneWithDialog: return i18n("Open file with:"); case OpenSeveralWithDialog: return i18n("Open files with:"); default: return ""; } } void FileContent::setFileName(const QString &fileName) { NoteContent::setFileName(fileName); QUrl url = QUrl::fromLocalFile(fullPath()); if (linkLook()->previewEnabled()) m_linkDisplayItem.linkDisplay().setLink(fileName, NoteFactory::iconForURL(url), linkLook(), note()->font()); // FIXME: move iconForURL outside of NoteFactory !!!!! else m_linkDisplayItem.linkDisplay().setLink(fileName, NoteFactory::iconForURL(url), QPixmap(), linkLook(), note()->font()); startFetchingUrlPreview(); contentChanged(m_linkDisplayItem.linkDisplay().minWidth()); } void FileContent::linkLookChanged() { fontChanged(); // setFileName(fileName()); // startFetchingUrlPreview(); } void FileContent::newPreview(const KFileItem &, const QPixmap &preview) { LinkLook *linkLook = this->linkLook(); m_linkDisplayItem.linkDisplay().setLink(fileName(), NoteFactory::iconForURL(QUrl::fromLocalFile(fullPath())), (linkLook->previewEnabled() ? preview : QPixmap()), linkLook, note()->font()); contentChanged(m_linkDisplayItem.linkDisplay().minWidth()); } void FileContent::removePreview(const KFileItem &ki) { newPreview(ki, QPixmap()); } void FileContent::startFetchingUrlPreview() { /* KUrl url(fullPath()); LinkLook *linkLook = this->linkLook(); // delete m_previewJob; if (!url.isEmpty() && linkLook->previewSize() > 0) { QUrl filteredUrl = NoteFactory::filteredURL(url);//KURIFilter::self()->filteredURI(url); KUrl::List urlList; urlList.append(filteredUrl); m_previewJob = KIO::filePreview(urlList, linkLook->previewSize(), linkLook->previewSize(), linkLook->iconSize()); connect(m_previewJob, SIGNAL(gotPreview(const KFileItem&, const QPixmap&)), this, SLOT(newPreview(const KFileItem&, const QPixmap&))); connect(m_previewJob, SIGNAL(failed(const KFileItem&)), this, SLOT(removePreview(const KFileItem&))); } */ } void FileContent::exportToHTML(HTMLExporter *exporter, int indent) { QString spaces; QString fileName = exporter->copyFile(fullPath(), true); exporter->stream << m_linkDisplayItem.linkDisplay().toHtml(exporter, QUrl::fromLocalFile(exporter->dataFolderName + fileName), "").replace("\n", '\n' + spaces.fill(' ', indent + 1)); } /** class SoundContent: */ SoundContent::SoundContent(Note *parent, const QString &fileName) : FileContent(parent, fileName) { setFileName(fileName); music = new Phonon::MediaObject(this); music->setCurrentSource(Phonon::MediaSource(fullPath())); Phonon::AudioOutput *audioOutput = new Phonon::AudioOutput(Phonon::MusicCategory, this); Phonon::Path path = Phonon::createPath(music, audioOutput); connect(music, SIGNAL(stateChanged(Phonon::State, Phonon::State)), this, SLOT(stateChanged(Phonon::State, Phonon::State))); } void SoundContent::stateChanged(Phonon::State newState, Phonon::State oldState) { qDebug() << "stateChanged " << oldState << " to " << newState; } QString SoundContent::zoneTip(int zone) { return (zone == Note::Custom0 ? i18n("Open this sound") : QString()); } void SoundContent::setHoveredZone(int oldZone, int newZone) { if (newZone == Note::Custom0 || newZone == Note::Content) { // Start the sound preview: if (oldZone != Note::Custom0 && oldZone != Note::Content) { // Don't restart if it was already in one of those zones if (music->state() == 1) { music->play(); } } } else { // Stop the sound preview, if it was started: if (music->state() != 1) { music->stop(); // delete music;//TODO implement this in slot connected with music alted signal // music = 0; } } } QString SoundContent::messageWhenOpening(OpenMessage where) { switch (where) { case OpenOne: return i18n("Opening sound..."); case OpenSeveral: return i18n("Opening sounds..."); case OpenOneWith: return i18n("Opening sound with..."); case OpenSeveralWith: return i18n("Opening sounds with..."); case OpenOneWithDialog: return i18n("Open sound with:"); case OpenSeveralWithDialog: return i18n("Open sounds with:"); default: return ""; } } /** class LinkContent: */ LinkContent::LinkContent(Note *parent, const QUrl &url, const QString &title, const QString &icon, bool autoTitle, bool autoIcon) : NoteContent(parent) , m_linkDisplayItem(parent) , m_access_manager(0) , m_acceptingData(false) , m_previewJob(0) { setLink(url, title, icon, autoTitle, autoIcon); if (parent) { parent->addToGroup(&m_linkDisplayItem); m_linkDisplayItem.setPos(parent->contentX(), Note::NOTE_MARGIN); } } LinkContent::~LinkContent() { if (note()) note()->removeFromGroup(&m_linkDisplayItem); delete m_access_manager; } qreal LinkContent::setWidthAndGetHeight(qreal width) { m_linkDisplayItem.linkDisplay().setWidth(width); return m_linkDisplayItem.linkDisplay().height(); } void LinkContent::saveToNode(QXmlStreamWriter &stream) { stream.writeStartElement("content"); stream.writeAttribute("title", title()); stream.writeAttribute("icon", icon()); stream.writeAttribute("autoIcon", (autoIcon() ? "true" : "false")); stream.writeAttribute("autoTitle", (autoTitle() ? "true" : "false")); stream.writeCharacters(url().toDisplayString()); stream.writeEndElement(); } void LinkContent::toolTipInfos(QStringList *keys, QStringList *values) { keys->append(i18n("Target")); values->append(m_url.toDisplayString()); } int LinkContent::zoneAt(const QPointF &pos) { return (m_linkDisplayItem.linkDisplay().iconButtonAt(pos) ? 0 : Note::Custom0); } QRectF LinkContent::zoneRect(int zone, const QPointF & /*pos*/) { QRectF linkRect = m_linkDisplayItem.linkDisplay().iconButtonRect(); if (zone == Note::Custom0) return QRectF(linkRect.width(), 0, note()->width(), note()->height()); // Too wide and height, but it will be clipped by Note::zoneRect() else if (zone == Note::Content) return linkRect; else return QRectF(); } QString LinkContent::zoneTip(int zone) { return (zone == Note::Custom0 ? i18n("Open this link") : QString()); } Qt::CursorShape LinkContent::cursorFromZone(int zone) const { if (zone == Note::Custom0) return Qt::PointingHandCursor; return Qt::ArrowCursor; } QString LinkContent::statusBarMessage(int zone) { if (zone == Note::Custom0 || zone == Note::Content) return m_url.toDisplayString(); else return ""; } QUrl LinkContent::urlToOpen(bool /*with*/) { return NoteFactory::filteredURL(url()); // KURIFilter::self()->filteredURI(url()); } QString LinkContent::messageWhenOpening(OpenMessage where) { if (url().isEmpty()) return i18n("Link have no URL to open."); switch (where) { case OpenOne: return i18n("Opening link target..."); case OpenSeveral: return i18n("Opening link targets..."); case OpenOneWith: return i18n("Opening link target with..."); case OpenSeveralWith: return i18n("Opening link targets with..."); case OpenOneWithDialog: return i18n("Open link target with:"); case OpenSeveralWithDialog: return i18n("Open link targets with:"); default: return ""; } } void LinkContent::setLink(const QUrl &url, const QString &title, const QString &icon, bool autoTitle, bool autoIcon) { m_autoTitle = autoTitle; m_autoIcon = autoIcon; m_url = NoteFactory::filteredURL(url); // KURIFilter::self()->filteredURI(url); m_title = (autoTitle ? NoteFactory::titleForURL(m_url) : title); m_icon = (autoIcon ? NoteFactory::iconForURL(m_url) : icon); LinkLook *look = LinkLook::lookForURL(m_url); if (look->previewEnabled()) m_linkDisplayItem.linkDisplay().setLink(m_title, m_icon, look, note()->font()); else m_linkDisplayItem.linkDisplay().setLink(m_title, m_icon, QPixmap(), look, note()->font()); startFetchingUrlPreview(); if (autoTitle) startFetchingLinkTitle(); contentChanged(m_linkDisplayItem.linkDisplay().minWidth()); } void LinkContent::linkLookChanged() { fontChanged(); } void LinkContent::newPreview(const KFileItem &, const QPixmap &preview) { LinkLook *linkLook = LinkLook::lookForURL(url()); m_linkDisplayItem.linkDisplay().setLink(title(), icon(), (linkLook->previewEnabled() ? preview : QPixmap()), linkLook, note()->font()); contentChanged(m_linkDisplayItem.linkDisplay().minWidth()); } void LinkContent::removePreview(const KFileItem &ki) { newPreview(ki, QPixmap()); } // QHttp slots for getting link title void LinkContent::httpReadyRead() { if (!m_acceptingData) return; // Check for availability qint64 bytesAvailable = m_reply->bytesAvailable(); if (bytesAvailable <= 0) return; QByteArray buf = m_reply->read(bytesAvailable); m_httpBuff.append(buf); // Stop at 10k bytes if (m_httpBuff.length() > 10000) { m_acceptingData = false; m_reply->abort(); endFetchingLinkTitle(); } } void LinkContent::httpDone(QNetworkReply *reply) { if (m_acceptingData) { m_acceptingData = false; endFetchingLinkTitle(); } // If all done, close and delete the reply. reply->deleteLater(); } void LinkContent::startFetchingLinkTitle() { QUrl newUrl = this->url(); // If this is not an HTTP request, just ignore it. if (newUrl.scheme() == "http") { // If we have no access_manager, create one. if (m_access_manager == 0) { m_access_manager = new KIO::Integration::AccessManager(this); connect(m_access_manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(httpDone(QNetworkReply *))); } // If no explicit port, default to port 80. if (newUrl.port() == 0) newUrl.setPort(80); // If no path or query part, default to / if ((newUrl.path() + newUrl.query()).isEmpty()) newUrl = QUrl::fromLocalFile("/"); // Issue request m_reply = m_access_manager->get(QNetworkRequest(newUrl)); m_acceptingData = true; connect(m_reply, SIGNAL(readyRead()), this, SLOT(httpReadyRead())); } } // Code duplicated from FileContent::startFetchingUrlPreview() void LinkContent::startFetchingUrlPreview() { QUrl url = this->url(); LinkLook *linkLook = LinkLook::lookForURL(this->url()); // delete m_previewJob; if (!url.isEmpty() && linkLook->previewSize() > 0) { QUrl filteredUrl = NoteFactory::filteredURL(url); // KURIFilter::self()->filteredURI(url); QList urlList; urlList.append(filteredUrl); m_previewJob = KIO::filePreview(urlList, linkLook->previewSize(), linkLook->previewSize(), linkLook->iconSize()); connect(m_previewJob, SIGNAL(gotPreview(const KFileItem &, const QPixmap &)), this, SLOT(newPreview(const KFileItem &, const QPixmap &))); connect(m_previewJob, SIGNAL(failed(const KFileItem &)), this, SLOT(removePreview(const KFileItem &))); } } void LinkContent::endFetchingLinkTitle() { if (m_httpBuff.length() > 0) { decodeHtmlTitle(); m_httpBuff.clear(); } else DEBUG_WIN << "LinkContent: empty buffer on endFetchingLinkTitle for " + m_url.toString(); } void LinkContent::exportToHTML(HTMLExporter *exporter, int indent) { QString linkTitle = title(); // TODO: // // Append address (useful for print version of the page/basket): // if (exportData.formatForImpression && (!autoTitle() && title() != NoteFactory::titleForURL(url().toDisplayString()))) { // // The address is on a new line, unless title is empty (empty lines was replaced by  ): // if (linkTitle == " "/*" "*/) // linkTitle = url().toDisplayString()/*""*/; // else // linkTitle = linkTitle + " <" + url().toDisplayString() + ">"/*+ "
    "*/; // //linkTitle += "" + url().toDisplayString() + ""; // } QUrl linkURL; /* QFileInfo fInfo(url().path()); // DEBUG_WIN << url().path() // << "IsFile:" + QString::number(fInfo.isFile()) // << "IsDir:" + QString::number(fInfo.isDir()); if (exportData.embedLinkedFiles && fInfo.isFile()) { // DEBUG_WIN << "Embed file"; linkURL = exportData.dataFolderName + BasketScene::copyFile(url().path(), exportData.dataFolderPath, true); } else if (exportData.embedLinkedFolders && fInfo.isDir()) { // DEBUG_WIN << "Embed folder"; linkURL = exportData.dataFolderName + BasketScene::copyFile(url().path(), exportData.dataFolderPath, true); } else { // DEBUG_WIN << "Embed LINK"; */ linkURL = url(); /* } */ QString spaces; exporter->stream << m_linkDisplayItem.linkDisplay().toHtml(exporter, linkURL, linkTitle).replace("\n", '\n' + spaces.fill(' ', indent + 1)); } /** class CrossReferenceContent: */ CrossReferenceContent::CrossReferenceContent(Note *parent, const QUrl &url, const QString &title, const QString &icon) : NoteContent(parent) , m_linkDisplayItem(parent) { this->setCrossReference(url, title, icon); if (parent) parent->addToGroup(&m_linkDisplayItem); } CrossReferenceContent::~CrossReferenceContent() { if (note()) note()->removeFromGroup(&m_linkDisplayItem); } qreal CrossReferenceContent::setWidthAndGetHeight(qreal width) { m_linkDisplayItem.linkDisplay().setWidth(width); return m_linkDisplayItem.linkDisplay().height(); } void CrossReferenceContent::saveToNode(QXmlStreamWriter &stream) { stream.writeStartElement("content"); stream.writeAttribute("title", title()); stream.writeAttribute("icon", icon()); stream.writeCharacters(url().toDisplayString()); stream.writeEndElement(); } void CrossReferenceContent::toolTipInfos(QStringList *keys, QStringList *values) { keys->append(i18n("Target")); values->append(m_url.toDisplayString()); } int CrossReferenceContent::zoneAt(const QPointF &pos) { return (m_linkDisplayItem.linkDisplay().iconButtonAt(pos) ? 0 : Note::Custom0); } QRectF CrossReferenceContent::zoneRect(int zone, const QPointF & /*pos*/) { QRectF linkRect = m_linkDisplayItem.linkDisplay().iconButtonRect(); if (zone == Note::Custom0) return QRectF(linkRect.width(), 0, note()->width(), note()->height()); // Too wide and height, but it will be clipped by Note::zoneRect() else if (zone == Note::Content) return linkRect; else return QRectF(); } QString CrossReferenceContent::zoneTip(int zone) { return (zone == Note::Custom0 ? i18n("Open this link") : QString()); } Qt::CursorShape CrossReferenceContent::cursorFromZone(int zone) const { if (zone == Note::Custom0) return Qt::PointingHandCursor; return Qt::ArrowCursor; } QString CrossReferenceContent::statusBarMessage(int zone) { if (zone == Note::Custom0 || zone == Note::Content) return i18n("Link to %1", this->title()); else return ""; } QUrl CrossReferenceContent::urlToOpen(bool /*with*/) { return m_url; } QString CrossReferenceContent::messageWhenOpening(OpenMessage where) { if (url().isEmpty()) return i18n("Link has no basket to open."); switch (where) { case OpenOne: return i18n("Opening basket..."); default: return ""; } } void CrossReferenceContent::setLink(const QUrl &url, const QString &title, const QString &icon) { this->setCrossReference(url, title, icon); } void CrossReferenceContent::setCrossReference(const QUrl &url, const QString &title, const QString &icon) { m_url = url; m_title = (title.isEmpty() ? url.url() : title); m_icon = icon; LinkLook *look = LinkLook::crossReferenceLook; m_linkDisplayItem.linkDisplay().setLink(m_title, m_icon, look, note()->font()); contentChanged(m_linkDisplayItem.linkDisplay().minWidth()); } void CrossReferenceContent::linkLookChanged() { fontChanged(); } void CrossReferenceContent::exportToHTML(HTMLExporter *exporter, int /*indent*/) { QString url = m_url.url(); QString title; if (url.startsWith(QLatin1String("basket://"))) url = url.mid(9, url.length() - 9); if (url.endsWith('/')) url = url.left(url.length() - 1); BasketScene *basket = Global::bnpView->basketForFolderName(url); if (!basket) title = "unknown basket"; else title = basket->basketName(); // if the basket we're trying to link to is the basket that was exported then // we have to use a special way to refer to it for the links. if (basket == exporter->exportedBasket) url = "../../" + exporter->fileName; else { // if we're in the exported basket then the links have to include // the sub directories. if (exporter->currentBasket == exporter->exportedBasket) url.prepend(exporter->basketsFolderName); url.append(".html"); } QString linkIcon = exporter->iconsFolderName + exporter->copyIcon(m_icon, LinkLook::crossReferenceLook->iconSize()); linkIcon = QString("\"\"").arg(linkIcon); exporter->stream << QString("%2 %3").arg(url, linkIcon, title); } /** class LauncherContent: */ LauncherContent::LauncherContent(Note *parent, const QString &fileName) : NoteContent(parent, fileName) , m_linkDisplayItem(parent) { basket()->addWatchedFile(fullPath()); loadFromFile(/*lazyLoad=*/false); if (parent) { parent->addToGroup(&m_linkDisplayItem); m_linkDisplayItem.setPos(parent->contentX(), Note::NOTE_MARGIN); } } LauncherContent::~LauncherContent() { if (note()) note()->removeFromGroup(&m_linkDisplayItem); } qreal LauncherContent::setWidthAndGetHeight(qreal width) { m_linkDisplayItem.linkDisplay().setWidth(width); return m_linkDisplayItem.linkDisplay().height(); } bool LauncherContent::loadFromFile(bool /*lazyLoad*/) // TODO: saveToFile() ?? Is it possible? { DEBUG_WIN << "Loading LauncherContent From " + basket()->folderName() + fileName(); KService service(fullPath()); setLauncher(service.name(), service.icon(), service.exec()); return true; } void LauncherContent::toolTipInfos(QStringList *keys, QStringList *values) { KService service(fullPath()); QString exec = service.exec(); if (service.terminal()) exec = i18n("%1 (run in terminal)", exec); if (!service.comment().isEmpty() && service.comment() != service.name()) { keys->append(i18n("Comment")); values->append(service.comment()); } keys->append(i18n("Command")); values->append(exec); } int LauncherContent::zoneAt(const QPointF &pos) { return (m_linkDisplayItem.linkDisplay().iconButtonAt(pos) ? 0 : Note::Custom0); } QRectF LauncherContent::zoneRect(int zone, const QPointF & /*pos*/) { QRectF linkRect = m_linkDisplayItem.linkDisplay().iconButtonRect(); if (zone == Note::Custom0) return QRectF(linkRect.width(), 0, note()->width(), note()->height()); // Too wide and height, but it will be clipped by Note::zoneRect() else if (zone == Note::Content) return linkRect; else return QRectF(); } QString LauncherContent::zoneTip(int zone) { return (zone == Note::Custom0 ? i18n("Launch this application") : QString()); } Qt::CursorShape LauncherContent::cursorFromZone(int zone) const { if (zone == Note::Custom0) return Qt::PointingHandCursor; return Qt::ArrowCursor; } QUrl LauncherContent::urlToOpen(bool with) { if (KService(fullPath()).exec().isEmpty()) return QUrl(); return (with ? QUrl() : QUrl::fromLocalFile(fullPath())); // Can open the application, but not with another application :-) } QString LauncherContent::messageWhenOpening(OpenMessage where) { if (KService(fullPath()).exec().isEmpty()) return i18n("The launcher have no command to run."); switch (where) { case OpenOne: return i18n("Launching application..."); case OpenSeveral: return i18n("Launching applications..."); case OpenOneWith: case OpenSeveralWith: case OpenOneWithDialog: case OpenSeveralWithDialog: // TODO: "Open this application with this file as parameter"? default: return ""; } } void LauncherContent::setLauncher(const QString &name, const QString &icon, const QString &exec) { m_name = name; m_icon = icon; m_exec = exec; m_linkDisplayItem.linkDisplay().setLink(name, icon, LinkLook::launcherLook, note()->font()); contentChanged(m_linkDisplayItem.linkDisplay().minWidth()); } void LauncherContent::exportToHTML(HTMLExporter *exporter, int indent) { QString spaces; QString fileName = exporter->copyFile(fullPath(), /*createIt=*/true); exporter->stream << m_linkDisplayItem.linkDisplay().toHtml(exporter, QUrl::fromLocalFile(exporter->dataFolderName + fileName), "").replace("\n", '\n' + spaces.fill(' ', indent + 1)); } /** class ColorItem: */ const int ColorItem::RECT_MARGIN = 2; ColorItem::ColorItem(Note *parent, const QColor &color) : QGraphicsItem(parent) , m_note(parent) { setColor(color); } void ColorItem::setColor(const QColor &color) { m_color = color; m_textRect = QFontMetrics(m_note->font()).boundingRect(m_color.name()); } QRectF ColorItem::boundingRect() const { qreal rectHeight = (m_textRect.height() + 2) * 3 / 2; qreal rectWidth = rectHeight * 14 / 10; // 1.4 times the height, like A4 papers. return QRectF(0, 0, rectWidth + RECT_MARGIN + m_textRect.width() + RECT_MARGIN, rectHeight); } void ColorItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { QRectF boundingRect = this->boundingRect(); qreal rectHeight = (m_textRect.height() + 2) * 3 / 2; qreal rectWidth = rectHeight * 14 / 10; // 1.4 times the height, like A4 papers. // FIXME: Duplicate from CommonColorSelector::drawColorRect: // Fill: painter->fillRect(1, 1, rectWidth - 2, rectHeight - 2, color()); // Stroke: QColor stroke = color().dark(125); painter->setPen(stroke); painter->drawLine(1, 0, rectWidth - 2, 0); painter->drawLine(0, 1, 0, rectHeight - 2); painter->drawLine(1, rectHeight - 1, rectWidth - 2, rectHeight - 1); painter->drawLine(rectWidth - 1, 1, rectWidth - 1, rectHeight - 2); // Round corners: painter->setPen(Tools::mixColor(color(), stroke)); painter->drawPoint(1, 1); painter->drawPoint(1, rectHeight - 2); painter->drawPoint(rectWidth - 2, rectHeight - 2); painter->drawPoint(rectWidth - 2, 1); // Draw the text: painter->setFont(m_note->font()); painter->setPen(m_note->palette().color(QPalette::Active, QPalette::WindowText)); painter->drawText(rectWidth + RECT_MARGIN, 0, m_textRect.width(), boundingRect.height(), Qt::AlignLeft | Qt::AlignVCenter, color().name()); } /** class ColorContent: */ ColorContent::ColorContent(Note *parent, const QColor &color) : NoteContent(parent) , m_colorItem(parent, color) { if (parent) { parent->addToGroup(&m_colorItem); m_colorItem.setPos(parent->contentX(), Note::NOTE_MARGIN); } } ColorContent::~ColorContent() { if (note()) note()->removeFromGroup(&m_colorItem); } qreal ColorContent::setWidthAndGetHeight(qreal /*width*/) // We do not need width because we can't word-break, and width is always >= minWidth() { return m_colorItem.boundingRect().height(); } void ColorContent::saveToNode(QXmlStreamWriter &stream) { stream.writeStartElement("content"); stream.writeCharacters(color().name()); stream.writeEndElement(); } void ColorContent::toolTipInfos(QStringList *keys, QStringList *values) { int hue, saturation, value; color().getHsv(&hue, &saturation, &value); keys->append(i18nc("RGB Colorspace: Red/Green/Blue", "RGB")); values->append(i18n("Red: %1, Green: %2, Blue: %3,", QString::number(color().red()), QString::number(color().green()), QString::number(color().blue()))); keys->append(i18nc("HSV Colorspace: Hue/Saturation/Value", "HSV")); values->append(i18n("Hue: %1, Saturation: %2, Value: %3,", QString::number(hue), QString::number(saturation), QString::number(value))); static QString cssColors[] = {"aqua", "00ffff", "black", "000000", "blue", "0000ff", "fuchsia", "ff00ff", "gray", "808080", "green", "008000", "lime", "00ff00", "maroon", "800000", "navy", "000080", "olive", "808000", "purple", "800080", "red", "ff0000", "silver", "c0c0c0", "teal", "008080", "white", "ffffff", "yellow", "ffff00"}; static QString cssExtendedColors[] = {"aliceblue", "f0f8ff", "antiquewhite", "faebd7", "aquamarine", "7fffd4", "azure", "f0ffff", "beige", "f5f5dc", "bisque", "ffe4c4", "blanchedalmond", "ffebcd", "blueviolet", "8a2be2", "brown", "a52a2a", "burlywood", "deb887", "cadetblue", "5f9ea0", "chartreuse", "7fff00", "chocolate", "d2691e", "coral", "ff7f50", "cornflowerblue", "6495ed", "cornsilk", "fff8dc", "crimson", "dc1436", "cyan", "00ffff", "darkblue", "00008b", "darkcyan", "008b8b", "darkgoldenrod", "b8860b", "darkgray", "a9a9a9", "darkgreen", "006400", "darkkhaki", "bdb76b", "darkmagenta", "8b008b", "darkolivegreen", "556b2f", "darkorange", "ff8c00", "darkorchid", "9932cc", "darkred", "8b0000", "darksalmon", "e9967a", "darkseagreen", "8fbc8f", "darkslateblue", "483d8b", "darkslategray", "2f4f4f", "darkturquoise", "00ced1", "darkviolet", "9400d3", "deeppink", "ff1493", "deepskyblue", "00bfff", "dimgray", "696969", "dodgerblue", "1e90ff", "firebrick", "b22222", "floralwhite", "fffaf0", "forestgreen", "228b22", "gainsboro", "dcdcdc", "ghostwhite", "f8f8ff", "gold", "ffd700", "goldenrod", "daa520", "greenyellow", "adff2f", "honeydew", "f0fff0", "hotpink", "ff69b4", "indianred", "cd5c5c", "indigo", "4b0082", "ivory", "fffff0", "khaki", "f0e68c", "lavender", "e6e6fa", "lavenderblush", "fff0f5", "lawngreen", "7cfc00", "lemonchiffon", "fffacd", "lightblue", "add8e6", "lightcoral", "f08080", "lightcyan", "e0ffff", "lightgoldenrodyellow", "fafad2", "lightgreen", "90ee90", "lightgrey", "d3d3d3", "lightpink", "ffb6c1", "lightsalmon", "ffa07a", "lightseagreen", "20b2aa", "lightskyblue", "87cefa", "lightslategray", "778899", "lightsteelblue", "b0c4de", "lightyellow", "ffffe0", "limegreen", "32cd32", "linen", "faf0e6", "magenta", "ff00ff", "mediumaquamarine", "66cdaa", "mediumblue", "0000cd", "mediumorchid", "ba55d3", "mediumpurple", "9370db", "mediumseagreen", "3cb371", "mediumslateblue", "7b68ee", "mediumspringgreen", "00fa9a", "mediumturquoise", "48d1cc", "mediumvioletred", "c71585", "midnightblue", "191970", "mintcream", "f5fffa", "mistyrose", "ffe4e1", "moccasin", "ffe4b5", "navajowhite", "ffdead", "oldlace", "fdf5e6", "olivedrab", "6b8e23", "orange", "ffa500", "orangered", "ff4500", "orchid", "da70d6", "palegoldenrod", "eee8aa", "palegreen", "98fb98", "paleturquoise", "afeeee", "palevioletred", "db7093", "papayawhip", "ffefd5", "peachpuff", "ffdab9", "peru", "cd853f", "pink", "ffc0cb", "plum", "dda0dd", "powderblue", "b0e0e6", "rosybrown", "bc8f8f", "royalblue", "4169e1", "saddlebrown", "8b4513", "salmon", "fa8072", "sandybrown", "f4a460", "seagreen", "2e8b57", "seashell", "fff5ee", "sienna", "a0522d", "skyblue", "87ceeb", "slateblue", "6a5acd", "slategray", "708090", "snow", "fffafa", "springgreen", "00ff7f", "steelblue", "4682b4", "tan", "d2b48c", "thistle", "d8bfd8", "tomato", "ff6347", "turquoise", "40e0d0", "violet", "ee82ee", "wheat", "f5deb3", "whitesmoke", "f5f5f5", "yellowgreen", "9acd32"}; QString colorHex = color().name().mid(1); // Take the hexadecimal name of the color, without the '#'. bool cssColorFound = false; for (int i = 0; i < 2 * 16; i += 2) { if (colorHex == cssColors[i + 1]) { keys->append(i18n("CSS Color Name")); values->append(cssColors[i]); cssColorFound = true; break; } } if (!cssColorFound) for (int i = 0; i < 2 * 124; i += 2) { if (colorHex == cssExtendedColors[i + 1]) { keys->append(i18n("CSS Extended Color Name")); values->append(cssExtendedColors[i]); break; } } keys->append(i18n("Is Web Color")); values->append(Tools::isWebColor(color()) ? i18n("Yes") : i18n("No")); } void ColorContent::setColor(const QColor &color) { m_colorItem.setColor(color); contentChanged(m_colorItem.boundingRect().width()); } void ColorContent::addAlternateDragObjects(QMimeData *dragObject) { dragObject->setColorData(color()); } void ColorContent::exportToHTML(HTMLExporter *exporter, int /*indent*/) { // FIXME: Duplicate from setColor(): TODO: rectSize() QRectF textRect = QFontMetrics(note()->font()).boundingRect(color().name()); int rectHeight = (textRect.height() + 2) * 3 / 2; int rectWidth = rectHeight * 14 / 10; // 1.4 times the height, like A4 papers. QString fileName = /*Tools::fileNameForNewFile(*/ QString("color_%1.png").arg(color().name().toLower().mid(1)) /*, exportData.iconsFolderPath)*/; QString fullPath = exporter->iconsFolderPath + fileName; QPixmap colorIcon(rectWidth, rectHeight); QPainter painter(&colorIcon); painter.setBrush(color()); painter.drawRoundedRect(0, 0, rectWidth, rectHeight, 2, 2); colorIcon.save(fullPath, "PNG"); QString iconHtml = QString("\"\"").arg(exporter->iconsFolderName + fileName, QString::number(colorIcon.width()), QString::number(colorIcon.height())); exporter->stream << iconHtml + ' ' + color().name(); } /** class UnknownItem: */ const qreal UnknownItem::DECORATION_MARGIN = 2; UnknownItem::UnknownItem(Note *parent) : QGraphicsItem(parent) , m_note(parent) { } QRectF UnknownItem::boundingRect() const { return QRectF(0, 0, m_textRect.width() + 2 * DECORATION_MARGIN, m_textRect.height() + 2 * DECORATION_MARGIN); } void UnknownItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { QPalette palette = m_note->basket()->palette(); qreal width = boundingRect().width(); qreal height = boundingRect().height(); painter->setPen(palette.color(QPalette::Active, QPalette::WindowText)); // Stroke: QColor stroke = Tools::mixColor(palette.color(QPalette::Active, QPalette::Background), palette.color(QPalette::Active, QPalette::WindowText)); painter->setPen(stroke); painter->drawLine(1, 0, width - 2, 0); painter->drawLine(0, 1, 0, height - 2); painter->drawLine(1, height - 1, width - 2, height - 1); painter->drawLine(width - 1, 1, width - 1, height - 2); // Round corners: painter->setPen(Tools::mixColor(palette.color(QPalette::Active, QPalette::Background), stroke)); painter->drawPoint(1, 1); painter->drawPoint(1, height - 2); painter->drawPoint(width - 2, height - 2); painter->drawPoint(width - 2, 1); painter->setPen(palette.color(QPalette::Active, QPalette::WindowText)); painter->drawText(DECORATION_MARGIN, DECORATION_MARGIN, width - 2 * DECORATION_MARGIN, height - 2 * DECORATION_MARGIN, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, m_mimeTypes); } void UnknownItem::setMimeTypes(QString mimeTypes) { m_mimeTypes = mimeTypes; m_textRect = QFontMetrics(m_note->font()).boundingRect(0, 0, 1, 500000, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, m_mimeTypes); } void UnknownItem::setWidth(qreal width) { prepareGeometryChange(); m_textRect = QFontMetrics(m_note->font()).boundingRect(0, 0, width, 500000, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, m_mimeTypes); } /** class UnknownContent: */ UnknownContent::UnknownContent(Note *parent, const QString &fileName) : NoteContent(parent, fileName) , m_unknownItem(parent) { if (parent) { parent->addToGroup(&m_unknownItem); m_unknownItem.setPos(parent->contentX(), Note::NOTE_MARGIN); } basket()->addWatchedFile(fullPath()); loadFromFile(/*lazyLoad=*/false); } UnknownContent::~UnknownContent() { if (note()) note()->removeFromGroup(&m_unknownItem); } qreal UnknownContent::setWidthAndGetHeight(qreal width) { m_unknownItem.setWidth(width); return m_unknownItem.boundingRect().height(); } bool UnknownContent::loadFromFile(bool /*lazyLoad*/) { DEBUG_WIN << "Loading UnknownContent From " + basket()->folderName() + fileName(); QString mimeTypes; QFile file(fullPath()); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&file); QString line; // Get the MIME-types names: do { if (!stream.atEnd()) { line = stream.readLine(); if (!line.isEmpty()) { if (mimeTypes.isEmpty()) mimeTypes += line; else mimeTypes += QString("\n") + line; } } } while (!line.isEmpty() && !stream.atEnd()); file.close(); } m_unknownItem.setMimeTypes(mimeTypes); contentChanged(m_unknownItem.boundingRect().width() + 1); return true; } void UnknownContent::addAlternateDragObjects(QMimeData *dragObject) { QFile file(fullPath()); if (file.open(QIODevice::ReadOnly)) { QDataStream stream(&file); // Get the MIME types names: QStringList mimes; QString line; do { if (!stream.atEnd()) { stream >> line; if (!line.isEmpty()) mimes.append(line); } } while (!line.isEmpty() && !stream.atEnd()); // Add the streams: quint64 size; // TODO: It was quint32 in version 0.5.0 ! QByteArray *array; for (int i = 0; i < mimes.count(); ++i) { // Get the size: stream >> size; // Allocate memory to retrieve size bytes and store them: array = new QByteArray; array->resize(size); stream.readRawData(array->data(), size); // Creata and add the QDragObject: dragObject->setData(mimes.at(i).toLatin1(), *array); delete array; // FIXME: Should we? } file.close(); } } void UnknownContent::exportToHTML(HTMLExporter *exporter, int indent) { QString spaces; exporter->stream << "
    " << mimeTypes().replace("\n", '\n' + spaces.fill(' ', indent + 1 + 1)) << "
    "; } void NoteFactory__loadNode(const QDomElement &content, const QString &lowerTypeName, Note *parent, bool lazyLoad) { if (lowerTypeName == "text") new TextContent(parent, content.text(), lazyLoad); else if (lowerTypeName == "html") new HtmlContent(parent, content.text(), lazyLoad); else if (lowerTypeName == "image") new ImageContent(parent, content.text(), lazyLoad); else if (lowerTypeName == "animation") new AnimationContent(parent, content.text(), lazyLoad); else if (lowerTypeName == "sound") new SoundContent(parent, content.text()); else if (lowerTypeName == "file") new FileContent(parent, content.text()); else if (lowerTypeName == "link") { bool autoTitle = content.attribute("title") == content.text(); bool autoIcon = content.attribute("icon") == NoteFactory::iconForURL(QUrl::fromUserInput(content.text())); autoTitle = XMLWork::trueOrFalse(content.attribute("autoTitle"), autoTitle); autoIcon = XMLWork::trueOrFalse(content.attribute("autoIcon"), autoIcon); new LinkContent(parent, QUrl::fromUserInput(content.text()), content.attribute("title"), content.attribute("icon"), autoTitle, autoIcon); } else if (lowerTypeName == "cross_reference") { new CrossReferenceContent(parent, QUrl::fromUserInput(content.text()), content.attribute("title"), content.attribute("icon")); } else if (lowerTypeName == "launcher") new LauncherContent(parent, content.text()); else if (lowerTypeName == "color") new ColorContent(parent, QColor(content.text())); else if (lowerTypeName == "unknown") new UnknownContent(parent, content.text()); } void LinkContent::decodeHtmlTitle() { KEncodingProber prober; prober.feed(m_httpBuff); // Fallback scheme: KEncodingProber - QTextCodec::codecForHtml - UTF-8 QTextCodec *textCodec; if (prober.confidence() > 0.5) textCodec = QTextCodec::codecForName(prober.encoding()); else textCodec = QTextCodec::codecForHtml(m_httpBuff, QTextCodec::codecForName("utf-8")); QString httpBuff = textCodec->toUnicode(m_httpBuff.data(), m_httpBuff.size()); // todo: this should probably strip odd html tags like   etc QRegExp reg("[\\s]*( )?([^<]+)[\\s]*", Qt::CaseInsensitive); reg.setMinimal(true); // qDebug() << *m_httpBuff << " bytes: " << bytes_read; if (reg.indexIn(httpBuff) >= 0) { m_title = reg.cap(2); m_autoTitle = false; setEdited(); // refresh the title setLink(url(), title(), icon(), autoTitle(), autoIcon()); } } diff --git a/src/notecontent.h b/src/notecontent.h index 2c0f390..70dc159 100644 --- a/src/notecontent.h +++ b/src/notecontent.h @@ -1,962 +1,949 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef NOTECONTENT_H #define NOTECONTENT_H #include #include #include #include #include #include #include "linklabel.h" class QDomElement; class QBuffer; class QColor; class QMimeData; class QMovie; class QPainter; class QPixmap; class QPoint; class QRect; class QString; class QStringList; class QTextDocument; class QWidget; class KFileItem; class QUrl; namespace KIO { class PreviewJob; } namespace Phonon { class MediaObject; } class BasketScene; class FilterData; class Note; /** * LinkDisplayItem is a QGraphicsItem using a LinkDisplay */ class LinkDisplayItem : public QGraphicsItem { public: explicit LinkDisplayItem(Note *parent) : m_note(parent) { } ~LinkDisplayItem() override { } QRectF boundingRect() const override; void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override; LinkDisplay &linkDisplay() { return m_linkDisplay; } private: LinkDisplay m_linkDisplay; Note *m_note; }; /** A list of numeric identifier for each note type. * Declare a variable with the type NoteType::Id and assign a value like NoteType::Text... * @author Sébastien Laoût */ namespace NoteType { enum Id { Group = 255, Text = 1, Html, Image, Animation, Sound, File, Link, CrossReference, Launcher, Color, Unknown }; // Always positive } /** Abstract base class for every content type of basket note. * It's a base class to represent those types: Text, Html, Image, Animation, Sound, File, Link, Launcher, Color, Unknown. * @author Sébastien Laoût */ class NoteContent { public: // Constructor and destructor: explicit NoteContent(Note *parent, const QString &fileName = ""); /// << Constructor. Inherited notes should call it to initialize the parent note. virtual ~NoteContent() { } /// << Virtual destructor. Reimplement it if you should destroy some data your custom types. // Simple Abstract Generic Methods: virtual NoteType::Id type() const = 0; /// << @return the internal number that identify that note type. virtual QString typeName() const = 0; /// << @return the translated type name to display in the user interface. virtual QString lowerTypeName() const = 0; /// << @return the type name in lowercase without space, for eg. saving. virtual QString toText(const QString &cuttedFullPath); /// << @return a plain text equivalent of the content. virtual QString toHtml(const QString &imageName, const QString &cuttedFullPath) = 0; /// << @return an HTML text equivalent of the content. @param imageName Save image in this Qt resource. virtual QPixmap toPixmap() { return QPixmap(); } /// << @return an image equivalent of the content. virtual void toLink(QUrl *url, QString *title, const QString &cuttedFullPath); /// << Set the link to the content. By default, it set them to fullPath() if useFile(). virtual bool useFile() const = 0; /// << @return true if it use a file to store the content. virtual bool canBeSavedAs() const = 0; /// << @return true if the content can be saved as a file by the user. virtual QString saveAsFilters() const = 0; /// << @return the filters for the user to choose a file destination to save the note as. virtual bool match(const FilterData &data) = 0; /// << @return true if the content match the filter criteria. // Complex Abstract Generic Methods: virtual void exportToHTML(HTMLExporter *exporter, int indent) = 0; /// << Export the note in an HTML file. virtual QString cssClass() const = 0; /// << @return the CSS class of the note when exported to HTML virtual qreal setWidthAndGetHeight(qreal width) = 0; /// << Relayout content with @p width (never less than minWidth()). @return its new height. virtual bool loadFromFile(bool /*lazyLoad*/) { return false; } /// << Load the content from the file. The default implementation does nothing. @see fileName(). virtual bool finishLazyLoad() { return false; } /// << Load what was not loaded by loadFromFile() if it was lazy-loaded virtual bool saveToFile() { return false; } /// << Save the content to the file. The default implementation does nothing. @see fileName(). virtual QString linkAt(const QPointF & /*pos*/) { return ""; } /// << @return the link anchor at position @p pos or "" if there is no link. virtual void saveToNode(QXmlStreamWriter &stream); /// << Save the note in the basket XML file. By default it store the filename if a file is used. virtual void fontChanged() = 0; /// << If your content display textual data, called when the font have changed (from tags or basket font) virtual void linkLookChanged() { } /// << If your content use LinkDisplay with preview enabled, reload the preview (can have changed size) virtual QString editToolTipText() const = 0; /// << @return "Edit this [text|image|...]" to put in the tooltip for the note's content zone. virtual void toolTipInfos(QStringList * /*keys*/, QStringList * /*values*/) { } /// << Get "key: value" couples to put in the tooltip for the note's content zone. // Custom Zones: /// Implement this if you want to store custom data. virtual int zoneAt(const QPointF & /*pos*/) { return 0; } /// << If your note-type have custom zones, @return the zone at @p pos or 0 if it's not a custom zone! virtual QRectF zoneRect(int /*zone*/, const QPointF & /*pos*/); /// << Idem, @return the rect of the custom zone virtual QString zoneTip(int /*zone*/) { return ""; } /// << Idem, @return the toolTip of the custom zone virtual Qt::CursorShape cursorFromZone(int /*zone*/) const { return Qt::ArrowCursor; } /// << Idem, @return the mouse cursor when it is over zone @p zone! virtual void setHoveredZone(int /*oldZone*/, int /*newZone*/) { } /// << If your note type need some feedback, you get notified of hovering changes here. virtual QString statusBarMessage(int /*zone*/) { return ""; } /// << @return the statusBar message to show for zone @p zone, or "" if nothing special have to be said. // Drag and Drop Content: virtual void serialize(QDataStream & /*stream*/) { } /// << Serialize the content in a QDragObject. If it consists of a file, it can be serialized for you. virtual bool shouldSerializeFile() { return useFile(); } /// << @return true if the dragging process should serialize the filename (and move the file if cutting). virtual void addAlternateDragObjects(QMimeData * /*dragObj*/) { } /// << If you offer more than toText/Html/Image/Link(), this will be called if this is the only selected. virtual QPixmap feedbackPixmap(qreal width, qreal height) = 0; /// << @return the pixmap to put under the cursor while dragging this object. virtual bool needSpaceForFeedbackPixmap() { return false; } /// << @return true if a space must be inserted before and after the DND feedback pixmap. // Content Edition: virtual int xEditorIndent() { return 0; } /// << If the editor should be indented (eg. to not cover an icon), return the number of pixels. // Open Content or File: virtual QUrl urlToOpen(bool /*with*/); /// << @return the URL to open the note, or an invalid QUrl if it's not openable. If @p with if false, it's a normal "Open". If it's true, it's for an "Open with..." action. The default /// implementation return the fullPath() if the note useFile() and nothing if not. enum OpenMessage { OpenOne, /// << Message to send to the statusbar when opening this note. OpenSeveral, /// << Message to send to the statusbar when opening several notes of this type. OpenOneWith, /// << Message to send to the statusbar when doing "Open With..." on this note. OpenSeveralWith, /// << Message to send to the statusbar when doing "Open With..." several notes of this type. OpenOneWithDialog, /// << Prompt-message of the "Open With..." dialog for this note. OpenSeveralWithDialog /// << Prompt-message of the "Open With..." dialog for several notes of this type. }; virtual QString messageWhenOpening(OpenMessage /*where*/) { return QString(); } /// << @return the message to display according to @p where or nothing if it can't be done. @see OpenMessage describing the nature of the message that should be returned... The default implementation return an empty string. NOTE: If /// urlToOpen() is invalid and messageWhenOpening() is not empty, then the user will be prompted to edit the note (with the message returned by messageWhenOpening()) for eg. being able to edit URL of a link if it's empty when opening /// it... virtual QString customOpenCommand() { return QString(); } /// << Reimplement this if your urlToOpen() should be opened with another application instead of the default KDE one. This choice should be left to the users in the setting (choice to use a custom app or not, and which app). // Common File Management: /// (and do save changes) and optionally hide the toolbar. virtual void setFileName(const QString &fileName); /// << Set the filename. Reimplement it if you eg. want to update the view when the filename is changed. bool trySetFileName(const QString &fileName); /// << Set the new filename and return true. Can fail and return false if a file with this fileName already exists. QString fullPath(); /// << Get the absolute path of the file where this content is stored on disk. QString fileName() const { return m_fileName; } /// << Get the file name where this content is stored (relative to the basket folder). @see fullPath(). qreal minWidth() const { return m_minWidth; } /// << Get the minimum width for this content. Note *note() { return m_note; } /// << Get the note managing this content. BasketScene *basket(); /// << Get the basket containing the note managing this content. virtual QGraphicsItem *graphicsItem() = 0; public: void setEdited(); /// << Mark the note as edited NOW: change the "last modification time and time" AND save the basket to XML file. protected: void contentChanged(qreal newMinWidth); /// << When the content has changed, inherited classes should call this to specify its new minimum size and trigger a basket relayout. private: Note *m_note; QString m_fileName; qreal m_minWidth; public: static const int FEEDBACK_DARKING; }; /** Real implementation of plain text notes: * @author Sébastien Laoût */ class TextContent : public NoteContent { public: // Constructor and destructor: TextContent(Note *parent, const QString &fileName, bool lazyLoad = false); ~TextContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toText(const QString & /*cuttedFullPath*/) override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; bool loadFromFile(bool lazyLoad) override; bool finishLazyLoad() override; bool saveToFile() override; QString linkAt(const QPointF &pos) override; void fontChanged() override; QString editToolTipText() const override; // Drag and Drop Content: QPixmap feedbackPixmap(qreal width, qreal height) override; // Open Content or File: QString messageWhenOpening(OpenMessage where) override; // QString customOpenCommand(); // Content-Specific Methods: void setText(const QString &text, bool lazyLoad = false); /// << Change the text note-content and relayout the note. QString text() { return m_graphicsTextItem.text(); } /// << @return the text note-content. QGraphicsItem *graphicsItem() override { return &m_graphicsTextItem; } protected: // QString m_text; // QTextDocument *m_simpleRichText; QGraphicsSimpleTextItem m_graphicsTextItem; }; #include /** Real implementation of rich text (HTML) notes: * @author Sébastien Laoût */ class HtmlContent : public NoteContent { public: // Constructor and destructor: HtmlContent(Note *parent, const QString &fileName, bool lazyLoad = false); ~HtmlContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toText(const QString & /*cuttedFullPath*/) override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; bool loadFromFile(bool lazyLoad) override; bool finishLazyLoad() override; bool saveToFile() override; QString linkAt(const QPointF &pos) override; void fontChanged() override; QString editToolTipText() const override; // Drag and Drop Content: QPixmap feedbackPixmap(qreal width, qreal height) override; // Open Content or File: QString messageWhenOpening(OpenMessage where) override; QString customOpenCommand() override; // Content-Specific Methods: void setHtml(const QString &html, bool lazyLoad = false); /// << Change the HTML note-content and relayout the note. QString html() { return m_html; } /// << @return the HTML note-content. QGraphicsItem *graphicsItem() override { return &m_graphicsTextItem; } protected: QString m_html; QString m_textEquivalent; // OPTIM_FILTER QTextDocument *m_simpleRichText; QGraphicsTextItem m_graphicsTextItem; }; /** Real implementation of image notes: * @author Sébastien Laoût */ class ImageContent : public NoteContent { public: // Constructor and destructor: ImageContent(Note *parent, const QString &fileName, bool lazyLoad = false); ~ImageContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; QPixmap toPixmap() override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; bool loadFromFile(bool lazyLoad) override; bool finishLazyLoad() override; bool saveToFile() override; void fontChanged() override; QString editToolTipText() const override; void toolTipInfos(QStringList *keys, QStringList *values) override; // Drag and Drop Content: QPixmap feedbackPixmap(qreal width, qreal height) override; bool needSpaceForFeedbackPixmap() override { return true; } // Open Content or File: QString messageWhenOpening(OpenMessage where) override; QString customOpenCommand() override; // Content-Specific Methods: void setPixmap(const QPixmap &pixmap); /// << Change the pixmap note-content and relayout the note. QPixmap pixmap() { return m_pixmapItem.pixmap(); } /// << @return the pixmap note-content. QByteArray data(); QGraphicsItem *graphicsItem() override { return &m_pixmapItem; } protected: QGraphicsPixmapItem m_pixmapItem; QByteArray m_format; }; /** Real implementation of animated image (GIF, MNG) notes: * @author Sébastien Laoût */ class AnimationContent : public QObject, public NoteContent // QObject to be able to receive QMovie signals { Q_OBJECT public: // Constructor and destructor: AnimationContent(Note *parent, const QString &fileName, bool lazyLoad = false); ~AnimationContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; QPixmap toPixmap() override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; void fontChanged() override; QString editToolTipText() const override; // Drag and Drop Content: QPixmap feedbackPixmap(qreal width, qreal height) override; bool needSpaceForFeedbackPixmap() override { return true; } // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; bool loadFromFile(bool lazyLoad) override; bool finishLazyLoad() override; bool saveToFile() override; // Open Content or File: QString messageWhenOpening(OpenMessage where) override; QString customOpenCommand() override; QGraphicsItem *graphicsItem() override { return &m_graphicsPixmap; } // Content-Specific Methods: bool startMovie(); protected slots: void movieUpdated(); void movieResized(); void movieFrameChanged(); protected: QBuffer *m_buffer; QMovie *m_movie; qreal m_currentWidth; QGraphicsPixmapItem m_graphicsPixmap; }; /** Real implementation of file notes: * @author Sébastien Laoût */ class FileContent : public QObject, public NoteContent { Q_OBJECT public: // Constructor and destructor: FileContent(Note *parent, const QString &fileName); ~FileContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; bool loadFromFile(bool /*lazyLoad*/) override; void fontChanged() override; void linkLookChanged() override; QString editToolTipText() const override; void toolTipInfos(QStringList *keys, QStringList *values) override; // Drag and Drop Content: QPixmap feedbackPixmap(qreal width, qreal height) override; // Custom Zones: int zoneAt(const QPointF &pos) override; QRectF zoneRect(int zone, const QPointF & /*pos*/) override; QString zoneTip(int zone) override; Qt::CursorShape cursorFromZone(int zone) const override; // Content Edition: int xEditorIndent() override; // Open Content or File: QString messageWhenOpening(OpenMessage where) override; // Content-Specific Methods: void setFileName(const QString &fileName) override; /// << Reimplemented to be able to relayout the note. virtual LinkLook *linkLook() { return LinkLook::fileLook; } QGraphicsItem *graphicsItem() override { return &m_linkDisplayItem; } protected: LinkDisplayItem m_linkDisplayItem; // File Preview Management: protected slots: void newPreview(const KFileItem &, const QPixmap &preview); void removePreview(const KFileItem &); void startFetchingUrlPreview(); protected: KIO::PreviewJob *m_previewJob; }; /** Real implementation of sound notes: * @author Sébastien Laoût */ class SoundContent : public FileContent // A sound is a file with just a bit different user interaction { Q_OBJECT public: // Constructor and destructor: SoundContent(Note *parent, const QString &fileName); // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; QString editToolTipText() const override; // Complex Generic Methods: QString cssClass() const override; // Custom Zones: QString zoneTip(int zone) override; void setHoveredZone(int oldZone, int newZone) override; // Open Content or File: QString messageWhenOpening(OpenMessage where) override; QString customOpenCommand() override; // Content-Specific Methods: LinkLook *linkLook() override { return LinkLook::soundLook; } Phonon::MediaObject *music; private slots: void stateChanged(Phonon::State, Phonon::State); }; /** Real implementation of link notes: * @author Sébastien Laoût */ class LinkContent : public QObject, public NoteContent { Q_OBJECT public: // Constructor and destructor: LinkContent(Note *parent, const QUrl &url, const QString &title, const QString &icon, bool autoTitle, bool autoIcon); ~LinkContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toText(const QString & /*cuttedFullPath*/) override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; void toLink(QUrl *url, QString *title, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; void saveToNode(QXmlStreamWriter &stream) override; void fontChanged() override; void linkLookChanged() override; QString editToolTipText() const override; void toolTipInfos(QStringList *keys, QStringList *values) override; // Drag and Drop Content: void serialize(QDataStream &stream) override; QPixmap feedbackPixmap(qreal width, qreal height) override; // Custom Zones: int zoneAt(const QPointF &pos) override; QRectF zoneRect(int zone, const QPointF & /*pos*/) override; QString zoneTip(int zone) override; Qt::CursorShape cursorFromZone(int zone) const override; QString statusBarMessage(int zone) override; // Open Content or File: QUrl urlToOpen(bool /*with*/) override; QString messageWhenOpening(OpenMessage where) override; // Content-Specific Methods: void setLink(const QUrl &url, const QString &title, const QString &icon, bool autoTitle, bool autoIcon); /// << Change the link and relayout the note. QUrl url() { return m_url; } /// << @return the URL of the link note-content. QString title() { return m_title; } /// << @return the displayed title of the link note-content. QString icon() { return m_icon; } /// << @return the displayed icon of the link note-content. bool autoTitle() { return m_autoTitle; } /// << @return if the title is auto-computed from the URL. bool autoIcon() { return m_autoIcon; } /// << @return if the icon is auto-computed from the URL. void startFetchingLinkTitle(); QGraphicsItem *graphicsItem() override { return &m_linkDisplayItem; } protected: QUrl m_url; QString m_title; QString m_icon; bool m_autoTitle; bool m_autoIcon; LinkDisplayItem m_linkDisplayItem; KIO::Integration::AccessManager *m_access_manager; QNetworkReply *m_reply; QByteArray m_httpBuff; ///< Accumulator for downloaded HTTP data with yet unknown encoding bool m_acceptingData; ///< When false, don't accept any HTTP data // File Preview Management: protected slots: void httpReadyRead(); void httpDone(QNetworkReply *reply); void newPreview(const KFileItem &, const QPixmap &preview); void removePreview(const KFileItem &); void startFetchingUrlPreview(); protected: KIO::PreviewJob *m_previewJob; private: void decodeHtmlTitle(); ///< Detect encoding of \p m_httpBuff and extract the title from HTML void endFetchingLinkTitle(); ///< Extract title and clear http buffer }; /** Real implementation of cross reference notes: * Copied and modified from LinkContent. * @author Brian C. Milco */ class CrossReferenceContent : public QObject, public NoteContent { Q_OBJECT public: // Constructor and destructor: CrossReferenceContent(Note *parent, const QUrl &url, const QString &title, const QString &icon); ~CrossReferenceContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toText(const QString & /*cuttedFullPath*/) override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; void toLink(QUrl *url, QString *title, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal) override; void saveToNode(QXmlStreamWriter &stream) override; void fontChanged() override; void linkLookChanged() override; QString editToolTipText() const override; void toolTipInfos(QStringList *keys, QStringList *values) override; // Drag and Drop Content: void serialize(QDataStream &stream) override; QPixmap feedbackPixmap(qreal width, qreal height) override; // Custom Zones: int zoneAt(const QPointF &pos) override; QRectF zoneRect(int zone, const QPointF & /*pos*/) override; QString zoneTip(int zone) override; Qt::CursorShape cursorFromZone(int zone) const override; QString statusBarMessage(int zone) override; // Open Content or File: QUrl urlToOpen(bool /*with*/) override; QString messageWhenOpening(OpenMessage where) override; // Content-Specific Methods: void setLink(const QUrl &url, const QString &title, const QString &icon); /// << Change the link and relayout the note. void setCrossReference(const QUrl &url, const QString &title, const QString &icon); QUrl url() { return m_url; } /// << @return the URL of the link note-content. QString title() { return m_title; } /// << @return the displayed title of the link note-content. QString icon() { return m_icon; } /// << @return the displayed icon of the link note-content. QGraphicsItem *graphicsItem() override { return &m_linkDisplayItem; } protected: QUrl m_url; QString m_title; QString m_icon; LinkDisplayItem m_linkDisplayItem; }; /** Real implementation of launcher notes: * @author Sébastien Laoût */ class LauncherContent : public NoteContent { public: // Constructor and destructor: LauncherContent(Note *parent, const QString &fileName); ~LauncherContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; void toLink(QUrl *url, QString *title, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; bool loadFromFile(bool /*lazyLoad*/) override; void fontChanged() override; QString editToolTipText() const override; void toolTipInfos(QStringList *keys, QStringList *values) override; // Drag and Drop Content: QPixmap feedbackPixmap(qreal width, qreal height) override; // Custom Zones: int zoneAt(const QPointF &pos) override; QRectF zoneRect(int zone, const QPointF & /*pos*/) override; QString zoneTip(int zone) override; Qt::CursorShape cursorFromZone(int zone) const override; // Open Content or File: QUrl urlToOpen(bool with) override; QString messageWhenOpening(OpenMessage where) override; // Content-Specific Methods: void setLauncher(const QString &name, const QString &icon, const QString &exec); /// << Change the launcher note-content and relayout the note. Normally called by loadFromFile (no save done). QString name() { return m_name; } /// << @return the URL of the launcher note-content. QString icon() { return m_icon; } /// << @return the displayed icon of the launcher note-content. QString exec() { return m_exec; } /// << @return the execute command line of the launcher note-content. // TODO: KService *service() ??? And store everything in thta service ? QGraphicsItem *graphicsItem() override { return &m_linkDisplayItem; } protected: QString m_name; // TODO: Store them in linkDisplay to gain place (idem for Link notes) QString m_icon; QString m_exec; LinkDisplayItem m_linkDisplayItem; }; /** * */ class ColorItem : public QGraphicsItem { public: ColorItem(Note *parent, const QColor &color); // virtual ~ColorItem(); virtual QColor color() { return m_color; } virtual void setColor(const QColor &color); QRectF boundingRect() const override; void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override; private: Note *m_note; QColor m_color; QRectF m_textRect; static const int RECT_MARGIN; }; /** Real implementation of color notes: * @author Sébastien Laoût */ class ColorContent : public NoteContent { public: // Constructor and destructor: ColorContent(Note *parent, const QColor &color); ~ColorContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toText(const QString & /*cuttedFullPath*/) override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; void saveToNode(QXmlStreamWriter &stream) override; void fontChanged() override; QString editToolTipText() const override; void toolTipInfos(QStringList *keys, QStringList *values) override; // Drag and Drop Content: void serialize(QDataStream &stream) override; QPixmap feedbackPixmap(qreal width, qreal height) override; bool needSpaceForFeedbackPixmap() override { return true; } void addAlternateDragObjects(QMimeData *dragObject) override; // Content-Specific Methods: void setColor(const QColor &color); /// << Change the color note-content and relayout the note. QColor color() { return m_colorItem.color(); } /// << @return the color note-content. QGraphicsItem *graphicsItem() override { return &m_colorItem; } protected: ColorItem m_colorItem; }; /** * */ class UnknownItem : public QGraphicsItem { public: UnknownItem(Note *parent); QRectF boundingRect() const override; void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override; virtual QString mimeTypes() { return m_mimeTypes; } virtual void setMimeTypes(QString mimeTypes); virtual void setWidth(qreal width); private: Note *m_note; QString m_mimeTypes; QRectF m_textRect; static const qreal DECORATION_MARGIN; }; /** Real implementation of unknown MIME-types dropped notes: * @author Sébastien Laoût */ class UnknownContent : public NoteContent { public: // Constructor and destructor: UnknownContent(Note *parent, const QString &fileName); ~UnknownContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toText(const QString & /*cuttedFullPath*/) override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; void toLink(QUrl *url, QString *title, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; bool loadFromFile(bool /*lazyLoad*/) override; void fontChanged() override; QString editToolTipText() const override; // Drag and Drop Content: bool shouldSerializeFile() override { return false; } void addAlternateDragObjects(QMimeData *dragObject) override; QPixmap feedbackPixmap(qreal width, qreal height) override; bool needSpaceForFeedbackPixmap() override { return true; } // Open Content or File: QUrl urlToOpen(bool /*with*/) override { return QUrl(); } QGraphicsItem *graphicsItem() override { return &m_unknownItem; } // Content-Specific Methods: QString mimeTypes() { return m_unknownItem.mimeTypes(); } /// << @return the list of MIME types this note-content contains. private: UnknownItem m_unknownItem; }; void NoteFactory__loadNode(const QDomElement &content, const QString &lowerTypeName, Note *parent, bool lazyLoad); #endif // NOTECONTENT_H diff --git a/src/notedrag.cpp b/src/notedrag.cpp index 75285fe..321109b 100644 --- a/src/notedrag.cpp +++ b/src/notedrag.cpp @@ -1,601 +1,588 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "notedrag.h" #include #include //For qApp->desktop() #include #include #include #include #include #include #include #include #include #include #include "basketscene.h" #include "global.h" #include "notefactory.h" #include "noteselection.h" #include "tools.h" /** NoteDrag */ const char *NoteDrag::NOTE_MIME_STRING = "application/x-basket-note"; QList NoteDrag::selectedNotes; void NoteDrag::createAndEmptyCuttingTmpFolder() { Tools::deleteRecursively(Global::tempCutFolder()); QDir dir; dir.mkdir(Global::tempCutFolder()); } QDrag *NoteDrag::dragObject(NoteSelection *noteList, bool cutting, QWidget *source) { if (noteList->count() <= 0) return 0; QDrag *multipleDrag = new QDrag(source); // The MimeSource: QMimeData *mimeData = new QMimeData; // Make sure the temporary folder exists and is empty (we delete previously moved file(s) (if exists) // since we override the content of the clipboard and previous file willn't be accessable anymore): createAndEmptyCuttingTmpFolder(); // The "Native Format" Serialization: QBuffer buffer; if (buffer.open(QIODevice::WriteOnly)) { QDataStream stream(&buffer); // First append a pointer to the basket: stream << (quint64)(noteList->firstStacked()->note->basket()); // And finally the notes themselves: serializeNotes(noteList, stream, cutting); // Append the object: buffer.close(); mimeData->setData(NOTE_MIME_STRING, buffer.buffer()); } // The "Other Flavors" Serialization: serializeText(noteList, multipleDrag); serializeHtml(noteList, multipleDrag); serializeImage(noteList, multipleDrag); serializeLinks(noteList, multipleDrag, cutting); // The Alternate Flavors: if (noteList->count() == 1) noteList->firstStacked()->note->content()->addAlternateDragObjects(mimeData); multipleDrag->setMimeData(mimeData); // If it is a drag, and not a copy/cut, add the feedback pixmap: if (source) setFeedbackPixmap(noteList, multipleDrag); return multipleDrag; } void NoteDrag::serializeNotes(NoteSelection *noteList, QDataStream &stream, bool cutting) { for (NoteSelection *node = noteList; node; node = node->next) { stream << (quint64)(node->note); if (node->firstChild) { stream << (quint64)(NoteType::Group) << (quint64)(node->note->groupWidth()) << (quint64)(node->note->isFolded()); serializeNotes(node->firstChild, stream, cutting); } else { NoteContent *content = node->note->content(); stream << (quint64)(content->type()) << (quint64)(node->note->groupWidth()); // Serialize file name, and move the file to a temporary place if the note is to be cut. // If note does not have file name, we append empty string to be able to easily decode the notes later: stream << content->fileName(); if (content->shouldSerializeFile()) { if (cutting) { // Move file in a temporary place: QString fullPath = Global::tempCutFolder() + Tools::fileNameForNewFile(content->fileName(), Global::tempCutFolder()); KIO::move(QUrl::fromLocalFile(content->fullPath()), QUrl::fromLocalFile(fullPath), KIO::HideProgressInfo); node->fullPath = fullPath; stream << fullPath; } else stream << content->fullPath(); } else stream << QString(""); stream << content->note()->addedDate() << content->note()->lastModificationDate(); content->serialize(stream); State::List states = node->note->states(); for (State::List::Iterator it = states.begin(); it != states.end(); ++it) stream << (quint64)(*it); stream << (quint64)0; } } stream << (quint64)0; // Mark the end of the notes in this group/hierarchy. } void NoteDrag::serializeText(NoteSelection *noteList, QDrag *multipleDrag) { QString textEquivalent; QString text; for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked()) { text = node->note->toText(node->fullPath); // note->toText() and not note->content()->toText() because the first one will also export the tags as text. if (!text.isEmpty()) textEquivalent += (!textEquivalent.isEmpty() ? "\n" : "") + text; } if (!textEquivalent.isEmpty()) { QMimeData *mimeData = new QMimeData; mimeData->setText(textEquivalent); multipleDrag->setMimeData(mimeData); } } void NoteDrag::serializeHtml(NoteSelection *noteList, QDrag *multipleDrag) { QString htmlEquivalent; QString html; for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked()) { html = node->note->content()->toHtml("", node->fullPath); if (!html.isEmpty()) htmlEquivalent += (!htmlEquivalent.isEmpty() ? "
    \n" : "") + html; } if (!htmlEquivalent.isEmpty()) { // Add HTML flavour: QMimeData *mimeData = new QMimeData; mimeData->setHtml(htmlEquivalent); // But also QTextEdit flavour, to be able to paste several notes to a text edit: QByteArray byteArray = ("

    " + htmlEquivalent).toLocal8Bit(); mimeData->setData("application/x-qrichtext", byteArray); multipleDrag->setMimeData(mimeData); } } void NoteDrag::serializeImage(NoteSelection *noteList, QDrag *multipleDrag) { QList pixmaps; QPixmap pixmap; for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked()) { pixmap = node->note->content()->toPixmap(); if (!pixmap.isNull()) pixmaps.append(pixmap); } if (!pixmaps.isEmpty()) { QPixmap pixmapEquivalent; if (pixmaps.count() == 1) pixmapEquivalent = pixmaps.first(); else { // Search the total size: int height = 0; int width = 0; for (QList::iterator it = pixmaps.begin(); it != pixmaps.end(); ++it) { height += (*it).height(); if ((*it).width() > width) width = (*it).width(); } // Create the image by painting all image into one big image: pixmapEquivalent = QPixmap(width, height); pixmapEquivalent.fill(Qt::white); QPainter painter(&pixmapEquivalent); height = 0; for (QList::iterator it = pixmaps.begin(); it != pixmaps.end(); ++it) { painter.drawPixmap(0, height, *it); height += (*it).height(); } } QMimeData *mimeData = new QMimeData; mimeData->setImageData(pixmapEquivalent.toImage()); multipleDrag->setMimeData(mimeData); } } void NoteDrag::serializeLinks(NoteSelection *noteList, QDrag *multipleDrag, bool cutting) { QList urls; QStringList titles; QUrl url; QString title; for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked()) { node->note->content()->toLink(&url, &title, node->fullPath); if (!url.isEmpty()) { urls.append(url); titles.append(title); } } if (!urls.isEmpty()) { // First, the standard text/uri-list MIME format: QMimeData *mimeData = new QMimeData; mimeData->setUrls(urls); // Then, also provide it in the Mozilla proprietary format (that also allow to add titles to URLs): // A version for Mozilla applications (convert to "theUrl\ntheTitle", into UTF-16): // FIXME: Does Mozilla support the drag of several URLs at once? // FIXME: If no, only provide that if there is only ONE URL. QString xMozUrl; for (int i = 0; i < urls.count(); ++i) xMozUrl += (xMozUrl.isEmpty() ? "" : "\n") + urls[i].toDisplayString() + '\n' + titles[i]; /* Code for only one: =============== xMozUrl = note->title() + "\n" + note->url().toDisplayString();*/ QByteArray baMozUrl; QTextStream stream(baMozUrl, QIODevice::WriteOnly); // It's UTF16 (aka UCS2), but with the first two order bytes // stream.setEncoding(QTextStream::RawUnicode); // It's UTF16 (aka UCS2), but with the first two order bytes // FIXME: find out if this is really equivalent, as https://doc.qt.io/archives/3.3/qtextstream.html pretends stream.setCodec("UTF-16"); stream << xMozUrl; mimeData->setData("text/x-moz-url", baMozUrl); if (cutting) { QByteArray arrayCut; arrayCut.resize(2); arrayCut[0] = '1'; arrayCut[1] = 0; mimeData->setData("application/x-kde-cutselection", arrayCut); } multipleDrag->setMimeData(mimeData); } } void NoteDrag::setFeedbackPixmap(NoteSelection *noteList, QDrag *multipleDrag) { QPixmap pixmap = feedbackPixmap(noteList); if (!pixmap.isNull()) { multipleDrag->setPixmap(pixmap); multipleDrag->setHotSpot(QPoint(-8, -8)); } } QPixmap NoteDrag::feedbackPixmap(NoteSelection *noteList) { if (noteList == 0) return QPixmap(); static const int MARGIN = 2; static const int SPACING = 1; QColor textColor = noteList->firstStacked()->note->basket()->textColor(); QColor backgroundColor = noteList->firstStacked()->note->basket()->backgroundColor().dark(NoteContent::FEEDBACK_DARKING); QList pixmaps; QList backgrounds; QList spaces; QPixmap pixmap; int height = 0; int width = 0; int i = 0; bool elipsisImage = false; QColor bgColor; bool needSpace; for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked(), ++i) { if (elipsisImage) { pixmap = QPixmap(7, 2); pixmap.fill(backgroundColor); QPainter painter(&pixmap); painter.setPen(textColor); painter.drawPoint(1, 1); painter.drawPoint(3, 1); painter.drawPoint(5, 1); painter.end(); bgColor = node->note->basket()->backgroundColor(); needSpace = false; } else { pixmap = node->note->content()->feedbackPixmap(/*maxWidth=*/qApp->desktop()->width() / 2, /*maxHeight=*/96); bgColor = node->note->backgroundColor(); needSpace = node->note->content()->needSpaceForFeedbackPixmap(); } if (!pixmap.isNull()) { if (pixmap.width() > width) width = pixmap.width(); pixmaps.append(pixmap); backgrounds.append(bgColor); spaces.append(needSpace); height += (i > 0 && needSpace ? 1 : 0) + pixmap.height() + SPACING + (needSpace ? 1 : 0); if (elipsisImage) break; if (height > qApp->desktop()->height() / 2) elipsisImage = true; } } if (!pixmaps.isEmpty()) { QPixmap result(MARGIN + width + MARGIN, MARGIN + height - SPACING + MARGIN - (spaces.last() ? 1 : 0)); QPainter painter(&result); // Draw all the images: height = MARGIN; QList::iterator it; QList::iterator it2; QList::iterator it3; int i = 0; for (it = pixmaps.begin(), it2 = backgrounds.begin(), it3 = spaces.begin(); it != pixmaps.end(); ++it, ++it2, ++it3, ++i) { if (i != 0 && (*it3)) { painter.fillRect(MARGIN, height, result.width() - 2 * MARGIN, SPACING, (*it2).dark(NoteContent::FEEDBACK_DARKING)); ++height; } painter.drawPixmap(MARGIN, height, *it); if ((*it).width() < width) painter.fillRect(MARGIN + (*it).width(), height, width - (*it).width(), (*it).height(), (*it2).dark(NoteContent::FEEDBACK_DARKING)); if (*it3) { painter.fillRect(MARGIN, height + (*it).height(), result.width() - 2 * MARGIN, SPACING, (*it2).dark(NoteContent::FEEDBACK_DARKING)); ++height; } painter.fillRect(MARGIN, height + (*it).height(), result.width() - 2 * MARGIN, SPACING, Tools::mixColor(textColor, backgroundColor)); height += (*it).height() + SPACING; } // Draw the border: painter.setPen(textColor); painter.drawLine(0, 0, result.width() - 1, 0); painter.drawLine(0, 0, 0, result.height() - 1); painter.drawLine(0, result.height() - 1, result.width() - 1, result.height() - 1); painter.drawLine(result.width() - 1, 0, result.width() - 1, result.height() - 1); // Draw the "lightly rounded" border: painter.setPen(Tools::mixColor(textColor, backgroundColor)); painter.drawPoint(0, 0); painter.drawPoint(0, result.height() - 1); painter.drawPoint(result.width() - 1, result.height() - 1); painter.drawPoint(result.width() - 1, 0); // Draw the background in the margin (the inside will be painted above, anyway): painter.setPen(backgroundColor); painter.drawLine(1, 1, result.width() - 2, 1); painter.drawLine(1, 1, 1, result.height() - 2); painter.drawLine(1, result.height() - 2, result.width() - 2, result.height() - 2); painter.drawLine(result.width() - 2, 1, result.width() - 2, result.height() - 2); // And assign the feedback pixmap to the drag object: // multipleDrag->setPixmap(result, QPoint(-8, -8)); return result; } return QPixmap(); } bool NoteDrag::canDecode(const QMimeData *source) { return source->hasFormat(NOTE_MIME_STRING); } BasketScene *NoteDrag::basketOf(const QMimeData *source) { QByteArray srcData = source->data(NOTE_MIME_STRING); QBuffer buffer(&srcData); if (buffer.open(QIODevice::ReadOnly)) { QDataStream stream(&buffer); // Get the parent basket: quint64 basketPointer; stream >> (quint64 &)basketPointer; return (BasketScene *)basketPointer; } else return 0; } QList NoteDrag::notesOf(QGraphicsSceneDragDropEvent *source) { /* FIXME: this code does not parse the stream properly (see NoteDrag::decode). Thus m_draggedNotes will contain many invalid pointer values. As a workaround, we use NoteDrag::selectedNotes now. */ QByteArray srcData = source->mimeData()->data(NOTE_MIME_STRING); QBuffer buffer(&srcData); if (buffer.open(QIODevice::ReadOnly)) { QDataStream stream(&buffer); // Get the parent basket: quint64 basketPointer; stream >> (quint64 &)basketPointer; // Get the note list: quint64 notePointer; QList notes; do { stream >> notePointer; if (notePointer != 0) notes.append((Note *)notePointer); } while (notePointer); // Done: return notes; } else return QList(); } void NoteDrag::saveNoteSelectionToList(NoteSelection *selection) { for (NoteSelection *sel = selection->firstStacked(); sel != NULL; sel = sel->nextStacked()) { if (sel->note->isGroup()) saveNoteSelectionToList(sel); else selectedNotes.append(sel->note); } } Note *NoteDrag::decode(const QMimeData *source, BasketScene *parent, bool moveFiles, bool moveNotes) { QByteArray srcData = source->data(NOTE_MIME_STRING); QBuffer buffer(&srcData); if (buffer.open(QIODevice::ReadOnly)) { QDataStream stream(&buffer); // Get the parent basket: quint64 basketPointer; stream >> (quint64 &)basketPointer; BasketScene *basket = (BasketScene *)basketPointer; // Decode the note hierarchy: Note *hierarchy = decodeHierarchy(stream, parent, moveFiles, moveNotes, basket); // In case we moved notes from one basket to another, save the source basket where notes were removed: basket->filterAgainDelayed(); // Delayed, because if a note is moved to the same basket, the note is not at its basket->save(); // new position yet, and the call to ensureNoteVisible would make the interface flicker!! return hierarchy; } else return 0; } Note *NoteDrag::decodeHierarchy(QDataStream &stream, BasketScene *parent, bool moveFiles, bool moveNotes, BasketScene *originalBasket) { quint64 notePointer; quint64 type; QString fileName; QString fullPath; QDateTime addedDate; QDateTime lastModificationDate; Note *firstNote = 0; // TODO: class NoteTreeChunk Note *lastInserted = 0; do { stream >> notePointer; if (notePointer == 0) return firstNote; Note *oldNote = (Note *)notePointer; Note *note = 0; quint64 groupWidth; stream >> type >> groupWidth; if (type == NoteType::Group) { note = new Note(parent); note->setZValue(-1); note->setGroupWidth(groupWidth); quint64 isFolded; stream >> isFolded; if (isFolded) note->toggleFolded(); if (moveNotes) { note->setX(oldNote->x()); // We don't move groups but re-create them (every children can to not be selected) note->setY(oldNote->y()); // We just set the position of the copied group so the animation seems as if the group is the same as (or a copy of) the old. note->setHeight(oldNote->height()); // Idem: the only use of Note::setHeight() parent->removeItem(oldNote); } Note *children = decodeHierarchy(stream, parent, moveFiles, moveNotes, originalBasket); if (children) { for (Note *n = children; n; n = n->next()) n->setParentNote(note); note->setFirstChild(children); } } else { stream >> fileName >> fullPath >> addedDate >> lastModificationDate; if (moveNotes) { originalBasket->unplugNote(oldNote); note = oldNote; if (note->basket() != parent && (!fileName.isEmpty() && !fullPath.isEmpty())) { QString newFileName = Tools::fileNameForNewFile(fileName, parent->fullPath()); note->content()->setFileName(newFileName); KIO::CopyJob *copyJob = KIO::move(QUrl::fromLocalFile(fullPath), QUrl::fromLocalFile(parent->fullPath() + newFileName), KIO::Overwrite | KIO::Resume | KIO::HideProgressInfo); parent->connect(copyJob, &KIO::CopyJob::copyingDone, parent, &BasketScene::slotCopyingDone2); } note->setGroupWidth(groupWidth); note->setParentNote(0); note->setPrev(0); note->setNext(0); note->setParentBasket(parent); NoteFactory::consumeContent(stream, (NoteType::Id)type); } else if ((note = NoteFactory::decodeContent(stream, (NoteType::Id)type, parent))) { note->setGroupWidth(groupWidth); note->setAddedDate(addedDate); note->setLastModificationDate(lastModificationDate); } else if (!fileName.isEmpty()) { // Here we are CREATING a new EMPTY file, so the name is RESERVED // (while dropping several files at once a filename cannot be used by two of them). // Later on, file_copy/file_move will copy/move the file to the new location. QString newFileName = Tools::fileNameForNewFile(fileName, parent->fullPath()); // NoteFactory::createFileForNewNote(parent, "", fileName); KIO::CopyJob *copyJob; if (moveFiles) { copyJob = KIO::move(QUrl::fromLocalFile(fullPath), QUrl::fromLocalFile(parent->fullPath() + newFileName), KIO::Overwrite | KIO::Resume | KIO::HideProgressInfo); } else { copyJob = KIO::copy(QUrl::fromLocalFile(fullPath), QUrl::fromLocalFile(parent->fullPath() + newFileName), KIO::Overwrite | KIO::Resume | KIO::HideProgressInfo); } parent->connect(copyJob, &KIO::CopyJob::copyingDone, parent, &BasketScene::slotCopyingDone2); note = NoteFactory::loadFile(newFileName, (NoteType::Id)type, parent); note->setGroupWidth(groupWidth); note->setAddedDate(addedDate); note->setLastModificationDate(lastModificationDate); } } // Retrieve the states (tags) and assign them to the note: if (note && note->content()) { quint64 statePointer; do { stream >> statePointer; if (statePointer) note->addState((State *)statePointer); } while (statePointer); } // Now that we have created the note, insert it: if (note) { if (!firstNote) firstNote = note; else { lastInserted->setNext(note); note->setPrev(lastInserted); } lastInserted = note; } } while (true); // We've done: return! return firstNote; } /** ExtendedTextDrag */ bool ExtendedTextDrag::decode(const QMimeData *e, QString &str) { QString subtype("plain"); return decode(e, str, subtype); } bool ExtendedTextDrag::decode(const QMimeData *e, QString &str, QString &subtype) { // Get the string: bool ok; str = e->text(); ok = !str.isNull(); // Test if it was a UTF-16 string (from eg. Mozilla): if (str.length() >= 2) { if ((str[0] == 0xFF && str[1] == 0xFE) || (str[0] == 0xFE && str[1] == 0xFF)) { QByteArray utf16 = e->data(QString("text/" + subtype).toLocal8Bit()); str = QTextCodec::codecForName("utf16")->toUnicode(utf16); return true; } } // Test if it was empty (sometimes, from GNOME or Mozilla) if (str.length() == 0 && subtype == "plain") { if (e->hasFormat("UTF8_STRING")) { QByteArray utf8 = e->data("UTF8_STRING"); str = QTextCodec::codecForName("utf8")->toUnicode(utf8); return true; } if (e->hasFormat("text/unicode")) { // FIXME: It's UTF-16 without order bytes!!! QByteArray utf16 = e->data("text/unicode"); str = QTextCodec::codecForName("utf16")->toUnicode(utf16); return true; } if (e->hasFormat("TEXT")) { // local encoding QByteArray text = e->data("TEXT"); str = QString(text); return true; } if (e->hasFormat("COMPOUND_TEXT")) { // local encoding QByteArray text = e->data("COMPOUND_TEXT"); str = QString(text); return true; } } return ok; } diff --git a/src/notedrag.h b/src/notedrag.h index 513ed39..c133d1d 100644 --- a/src/notedrag.h +++ b/src/notedrag.h @@ -1,92 +1,79 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef NOTEDRAG_H #define NOTEDRAG_H #include #include #include class QDataStream; class QDragEnterEvent; class QPixmap; class QString; class BasketScene; class Note; class NoteSelection; /** Dragging/Copying/Cutting Scenario: * - User select some notes and cut them; * - NoteDrag::toMultipleDrag() is called with a tree of the selected notes (see BasketScene::toSelectionTree()): * - This method create a new QDrag object, create a stream, * - And then browse all notes and call the virtual Note::serialize() with the stream as parameter for them to serialize their content in the "native format". * - This give the MIME type "application/x-basket-note" that will be used by the application to paste the notes exactly as they were. * - Then the method try to set alternate formats for the dragged objects: * - It call successively toText() for each notes and stack up the result so there is ONE big text flavour to add to the QDrag object * - It do the same with toHtml(), toImage() and toLink() to have those flavors as well, if possible... * - If there is only ONE copied note, addAlternateDragObjects() is called on it, so that Unknown objects can be dragged "as is". * - It's OK for the flavors. The method finally set the drag feedback pixmap by asking every selected notes to draw the content to a small pixmap. * - The pixmaps are joined to one big pixmap (but it should not exceed a defined size) and a border is drawn on this image. * * Pasting/Dropping Scenario: * * @author Sébastien Laoût */ class NoteDrag { protected: static void serializeNotes(NoteSelection *noteList, QDataStream &stream, bool cutting); static void serializeText(NoteSelection *noteList, QDrag *multipleDrag); static void serializeHtml(NoteSelection *noteList, QDrag *multipleDrag); static void serializeImage(NoteSelection *noteList, QDrag *multipleDrag); static void serializeLinks(NoteSelection *noteList, QDrag *multipleDrag, bool cutting); static void setFeedbackPixmap(NoteSelection *noteList, QDrag *multipleDrag); static Note *decodeHierarchy(QDataStream &stream, BasketScene *parent, bool moveFiles, bool moveNotes, BasketScene *originalBasket); public: static QPixmap feedbackPixmap(NoteSelection *noteList); static QDrag *dragObject(NoteSelection *noteList, bool cutting, QWidget *source = 0); static bool canDecode(const QMimeData *source); static Note *decode(const QMimeData *source, BasketScene *parent, bool moveFiles, bool moveNotes); static BasketScene *basketOf(const QMimeData *source); static QList notesOf(QGraphicsSceneDragDropEvent *source); static void saveNoteSelectionToList(NoteSelection *selection); ///< Traverse @p selection and save all note pointers to @p selectedNotes static void createAndEmptyCuttingTmpFolder(); static const char *NOTE_MIME_STRING; static QList selectedNotes; ///< The notes being selected and dragged }; /** QTextDrag with capabilities to drop GNOME and Mozilla texts * as well as UTF-16 texts even if it was supposed to be encoded * with local encoding! * @author Sébastien Laoût */ class ExtendedTextDrag : public QDrag { Q_OBJECT public: static bool decode(const QMimeData *e, QString &str); static bool decode(const QMimeData *e, QString &str, QString &subtype); }; #endif // NOTEDRAG_H diff --git a/src/noteedit.cpp b/src/noteedit.cpp index d001663..a90564e 100644 --- a/src/noteedit.cpp +++ b/src/noteedit.cpp @@ -1,1335 +1,1322 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "noteedit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "basketlistview.h" #include "basketscene.h" #include "focusedwidgets.h" #include "icon_names.h" #include "note.h" #include "notecontent.h" #include "notefactory.h" #include "settings.h" #include "tools.h" #include "variouswidgets.h" /** class NoteEditor: */ NoteEditor::NoteEditor(NoteContent *noteContent) { m_isEmpty = false; m_canceled = false; m_widget = 0; m_textEdit = 0; m_lineEdit = 0; m_noteContent = noteContent; } NoteEditor::~NoteEditor() { delete m_widget; } Note *NoteEditor::note() { return m_noteContent->note(); } void NoteEditor::setCursorTo(const QPointF &pos) { // clicked comes from the QMouseEvent, which is in item's coordinate system. if (m_textEdit) { QPointF currentPos = note()->mapFromScene(pos); QPointF deltaPos = m_textEdit->pos() - note()->pos(); m_textEdit->setTextCursor(m_textEdit->cursorForPosition((currentPos - deltaPos).toPoint())); } } void NoteEditor::startSelection(const QPointF &pos) { if (m_textEdit) { QPointF currentPos = note()->mapFromScene(pos); QPointF deltaPos = m_textEdit->pos() - note()->pos(); m_textEdit->setTextCursor(m_textEdit->cursorForPosition((currentPos - deltaPos).toPoint())); } } void NoteEditor::updateSelection(const QPointF &pos) { if (m_textEdit) { QPointF currentPos = note()->mapFromScene(pos); QPointF deltaPos = m_textEdit->pos() - note()->pos(); QTextCursor cursor = m_textEdit->cursorForPosition((currentPos - deltaPos).toPoint()); QTextCursor currentCursor = m_textEdit->textCursor(); // select the text currentCursor.setPosition(cursor.position(), QTextCursor::KeepAnchor); // update the cursor m_textEdit->setTextCursor(currentCursor); } } void NoteEditor::endSelection(const QPointF & /*pos*/) { // For TextEdit inside GraphicsScene selectionChanged() is only generated for the first selected char - // thus we need to call it manually after selection is finished if (FocusedTextEdit *textEdit = dynamic_cast(m_textEdit)) textEdit->onSelectionChanged(); } void NoteEditor::paste(const QPointF &pos, QClipboard::Mode mode) { if (FocusedTextEdit *textEdit = dynamic_cast(m_textEdit)) { setCursorTo(pos); textEdit->paste(mode); } } void NoteEditor::connectActions(BasketScene *scene) { if (m_textEdit) { connect(m_textEdit, SIGNAL(textChanged()), scene, SLOT(selectionChangedInEditor())); connect(m_textEdit, SIGNAL(textChanged()), scene, SLOT(contentChangedInEditor())); connect(m_textEdit, SIGNAL(textChanged()), scene, SLOT(placeEditorAndEnsureVisible())); connect(m_textEdit, SIGNAL(selectionChanged()), scene, SLOT(selectionChangedInEditor())); } else if (m_lineEdit) { connect(m_lineEdit, SIGNAL(textChanged(const QString &)), scene, SLOT(selectionChangedInEditor())); connect(m_lineEdit, SIGNAL(textChanged(const QString &)), scene, SLOT(contentChangedInEditor())); connect(m_lineEdit, SIGNAL(selectionChanged()), scene, SLOT(selectionChangedInEditor())); } } NoteEditor *NoteEditor::editNoteContent(NoteContent *noteContent, QWidget *parent) { TextContent *textContent = dynamic_cast(noteContent); if (textContent) return new TextEditor(textContent, parent); HtmlContent *htmlContent = dynamic_cast(noteContent); if (htmlContent) return new HtmlEditor(htmlContent, parent); ImageContent *imageContent = dynamic_cast(noteContent); if (imageContent) return new ImageEditor(imageContent, parent); AnimationContent *animationContent = dynamic_cast(noteContent); if (animationContent) return new AnimationEditor(animationContent, parent); FileContent *fileContent = dynamic_cast(noteContent); // Same for SoundContent if (fileContent) return new FileEditor(fileContent, parent); LinkContent *linkContent = dynamic_cast(noteContent); if (linkContent) return new LinkEditor(linkContent, parent); CrossReferenceContent *crossReferenceContent = dynamic_cast(noteContent); if (crossReferenceContent) return new CrossReferenceEditor(crossReferenceContent, parent); LauncherContent *launcherContent = dynamic_cast(noteContent); if (launcherContent) return new LauncherEditor(launcherContent, parent); ColorContent *colorContent = dynamic_cast(noteContent); if (colorContent) return new ColorEditor(colorContent, parent); UnknownContent *unknownContent = dynamic_cast(noteContent); if (unknownContent) return new UnknownEditor(unknownContent, parent); return 0; } void NoteEditor::setInlineEditor(QWidget *inlineEditor) { if (!m_widget) { m_widget = new QGraphicsProxyWidget(); } m_widget->setWidget(inlineEditor); m_widget->setZValue(500); // m_widget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); m_widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_textEdit = 0; m_lineEdit = 0; KTextEdit *textEdit = dynamic_cast(inlineEditor); if (textEdit) { m_textEdit = textEdit; } else { QLineEdit *lineEdit = dynamic_cast(inlineEditor); if (lineEdit) { m_lineEdit = lineEdit; } } } /** class TextEditor: */ TextEditor::TextEditor(TextContent *textContent, QWidget *parent) : NoteEditor(textContent) , m_textContent(textContent) { FocusedTextEdit *textEdit = new FocusedTextEdit(/*disableUpdatesOnKeyPress=*/true, parent); textEdit->setLineWidth(0); textEdit->setMidLineWidth(0); textEdit->setFrameStyle(QFrame::Box); QPalette palette; palette.setColor(textEdit->backgroundRole(), note()->backgroundColor()); palette.setColor(textEdit->foregroundRole(), note()->textColor()); textEdit->setPalette(palette); textEdit->setFont(note()->font()); textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); if (Settings::spellCheckTextNotes()) textEdit->setCheckSpellingEnabled(true); textEdit->setPlainText(m_textContent->text()); // Not sure if the following comment is still true // FIXME: Sometimes, the cursor flicker at ends before being positionned where clicked (because qApp->processEvents() I think) textEdit->moveCursor(QTextCursor::End); textEdit->verticalScrollBar()->setCursor(Qt::ArrowCursor); setInlineEditor(textEdit); connect(textEdit, SIGNAL(escapePressed()), this, SIGNAL(askValidation())); connect(textEdit, SIGNAL(mouseEntered()), this, SIGNAL(mouseEnteredEditorWidget())); connect(textEdit, SIGNAL(cursorPositionChanged()), textContent->note()->basket(), SLOT(editorCursorPositionChanged())); // In case it is a very big note, the top is displayed and Enter is pressed: the cursor is on bottom, we should enure it visible: QTimer::singleShot(0, textContent->note()->basket(), SLOT(editorCursorPositionChanged())); } TextEditor::~TextEditor() { delete graphicsWidget()->widget(); // TODO: delete that in validate(), so we can remove one method } void TextEditor::autoSave(bool toFileToo) { bool autoSpellCheck = true; if (toFileToo) { if (Settings::spellCheckTextNotes() != textEdit()->checkSpellingEnabled()) { Settings::setSpellCheckTextNotes(textEdit()->checkSpellingEnabled()); Settings::saveConfig(); } autoSpellCheck = textEdit()->checkSpellingEnabled(); textEdit()->setCheckSpellingEnabled(false); } m_textContent->setText(textEdit()->toPlainText()); if (toFileToo) { m_textContent->saveToFile(); m_textContent->setEdited(); textEdit()->setCheckSpellingEnabled(autoSpellCheck); } } void TextEditor::validate() { if (Settings::spellCheckTextNotes() != textEdit()->checkSpellingEnabled()) { Settings::setSpellCheckTextNotes(textEdit()->checkSpellingEnabled()); Settings::saveConfig(); } textEdit()->setCheckSpellingEnabled(false); if (textEdit()->document()->isEmpty()) setEmpty(); m_textContent->setText(textEdit()->toPlainText()); m_textContent->saveToFile(); m_textContent->setEdited(); note()->setWidth(0); } /** class HtmlEditor: */ HtmlEditor::HtmlEditor(HtmlContent *htmlContent, QWidget *parent) : NoteEditor(htmlContent) , m_htmlContent(htmlContent) { FocusedTextEdit *textEdit = new FocusedTextEdit(/*disableUpdatesOnKeyPress=*/true, parent); textEdit->setLineWidth(0); textEdit->setMidLineWidth(0); textEdit->setFrameStyle(QFrame::Box); textEdit->setAutoFormatting(Settings::autoBullet() ? QTextEdit::AutoAll : QTextEdit::AutoNone); QPalette palette; palette.setColor(textEdit->backgroundRole(), note()->backgroundColor()); palette.setColor(textEdit->foregroundRole(), note()->textColor()); textEdit->setPalette(palette); textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); textEdit->setHtml(Tools::tagCrossReferences(m_htmlContent->html(), /*userLink=*/true)); textEdit->moveCursor(QTextCursor::End); textEdit->verticalScrollBar()->setCursor(Qt::ArrowCursor); setInlineEditor(textEdit); connect(textEdit, SIGNAL(mouseEntered()), this, SIGNAL(mouseEnteredEditorWidget())); connect(textEdit, SIGNAL(escapePressed()), this, SIGNAL(askValidation())); connect(InlineEditors::instance()->richTextFont, SIGNAL(currentFontChanged(const QFont &)), this, SLOT(onFontSelectionChanged(const QFont &))); connect(InlineEditors::instance()->richTextFontSize, SIGNAL(sizeChanged(qreal)), textEdit, SLOT(setFontPointSize(qreal))); connect(InlineEditors::instance()->richTextColor, SIGNAL(activated(const QColor &)), textEdit, SLOT(setTextColor(const QColor &))); connect(InlineEditors::instance()->focusWidgetFilter, SIGNAL(escapePressed()), textEdit, SLOT(setFocus())); connect(InlineEditors::instance()->focusWidgetFilter, SIGNAL(returnPressed()), textEdit, SLOT(setFocus())); connect(InlineEditors::instance()->richTextFont, SIGNAL(activated(int)), textEdit, SLOT(setFocus())); connect(InlineEditors::instance()->richTextFontSize, SIGNAL(activated(int)), textEdit, SLOT(setFocus())); connect(textEdit, SIGNAL(cursorPositionChanged()), this, SLOT(cursorPositionChanged())); connect(textEdit, SIGNAL(currentCharFormatChanged(const QTextCharFormat &)), this, SLOT(charFormatChanged(const QTextCharFormat &))); // connect( textEdit, SIGNAL(currentVerticalAlignmentChanged(VerticalAlignment)), this, SLOT(slotVerticalAlignmentChanged()) ); connect(InlineEditors::instance()->richTextBold, SIGNAL(triggered(bool)), this, SLOT(setBold(bool))); connect(InlineEditors::instance()->richTextItalic, SIGNAL(triggered(bool)), textEdit, SLOT(setFontItalic(bool))); connect(InlineEditors::instance()->richTextUnderline, SIGNAL(triggered(bool)), textEdit, SLOT(setFontUnderline(bool))); connect(InlineEditors::instance()->richTextLeft, SIGNAL(triggered()), this, SLOT(setLeft())); connect(InlineEditors::instance()->richTextCenter, SIGNAL(triggered()), this, SLOT(setCentered())); connect(InlineEditors::instance()->richTextRight, SIGNAL(triggered()), this, SLOT(setRight())); connect(InlineEditors::instance()->richTextJustified, SIGNAL(triggered()), this, SLOT(setBlock())); // InlineEditors::instance()->richTextToolBar()->show(); cursorPositionChanged(); charFormatChanged(textEdit->currentCharFormat()); // QTimer::singleShot( 0, this, SLOT(cursorPositionChanged()) ); InlineEditors::instance()->enableRichTextToolBar(); connect(InlineEditors::instance()->richTextUndo, SIGNAL(triggered()), textEdit, SLOT(undo())); connect(InlineEditors::instance()->richTextRedo, SIGNAL(triggered()), textEdit, SLOT(redo())); connect(textEdit, SIGNAL(undoAvailable(bool)), InlineEditors::instance()->richTextUndo, SLOT(setEnabled(bool))); connect(textEdit, SIGNAL(redoAvailable(bool)), InlineEditors::instance()->richTextRedo, SLOT(setEnabled(bool))); connect(textEdit, SIGNAL(textChanged()), this, SLOT(editTextChanged())); InlineEditors::instance()->richTextUndo->setEnabled(false); InlineEditors::instance()->richTextRedo->setEnabled(false); connect(textEdit, SIGNAL(cursorPositionChanged()), htmlContent->note()->basket(), SLOT(editorCursorPositionChanged())); // In case it is a very big note, the top is displayed and Enter is pressed: the cursor is on bottom, we should enure it visible: QTimer::singleShot(0, htmlContent->note()->basket(), SLOT(editorCursorPositionChanged())); } void HtmlEditor::cursorPositionChanged() { InlineEditors::instance()->richTextFont->setCurrentFont(textEdit()->currentFont().family()); if (InlineEditors::instance()->richTextColor->color() != textEdit()->textColor()) InlineEditors::instance()->richTextColor->setColor(textEdit()->textColor()); InlineEditors::instance()->richTextBold->setChecked((textEdit()->fontWeight() >= QFont::Bold)); InlineEditors::instance()->richTextItalic->setChecked(textEdit()->fontItalic()); InlineEditors::instance()->richTextUnderline->setChecked(textEdit()->fontUnderline()); switch (textEdit()->alignment()) { default: case 1 /*Qt::AlignLeft*/: InlineEditors::instance()->richTextLeft->setChecked(true); break; case 2 /*Qt::AlignRight*/: InlineEditors::instance()->richTextRight->setChecked(true); break; case 4 /*Qt::AlignHCenter*/: InlineEditors::instance()->richTextCenter->setChecked(true); break; case 8 /*Qt::AlignJustify*/: InlineEditors::instance()->richTextJustified->setChecked(true); break; } } void HtmlEditor::editTextChanged() { // The following is a workaround for an apparent Qt bug. // When I start typing in a textEdit, the undo&redo actions are not enabled until I click // or move the cursor - probably, the signal undoAvailable() is not emitted. // So, I had to intervene and do that manually. InlineEditors::instance()->richTextUndo->setEnabled(textEdit()->document()->isUndoAvailable()); InlineEditors::instance()->richTextRedo->setEnabled(textEdit()->document()->isRedoAvailable()); } void HtmlEditor::charFormatChanged(const QTextCharFormat &format) { InlineEditors::instance()->richTextFontSize->setFontSize(format.font().pointSize()); } /*void HtmlEditor::slotVerticalAlignmentChanged(QTextEdit::VerticalAlignment align) { QTextEdit::VerticalAlignment align = textEdit() switch (align) { case KTextEdit::AlignSuperScript: InlineEditors::instance()->richTextSuper->setChecked(true); InlineEditors::instance()->richTextSub->setChecked(false); break; case KTextEdit::AlignSubScript: InlineEditors::instance()->richTextSuper->setChecked(false); InlineEditors::instance()->richTextSub->setChecked(true); break; default: InlineEditors::instance()->richTextSuper->setChecked(false); InlineEditors::instance()->richTextSub->setChecked(false); } NoteHtmlEditor::buttonToggled(int id) : case 106: if (isChecked && m_toolbar->isButtonOn(107)) m_toolbar->setButton(107, false); m_text->setVerticalAlignment(isChecked ? KTextEdit::AlignSuperScript : KTextEdit::AlignNormal); break; case 107: if (isChecked && m_toolbar->isButtonOn(106)) m_toolbar->setButton(106, false); m_text->setVerticalAlignment(isChecked ? KTextEdit::AlignSubScript : KTextEdit::AlignNormal); break; }*/ void HtmlEditor::setLeft() { textEdit()->setAlignment(Qt::AlignLeft); } void HtmlEditor::setRight() { textEdit()->setAlignment(Qt::AlignRight); } void HtmlEditor::setCentered() { textEdit()->setAlignment(Qt::AlignHCenter); } void HtmlEditor::setBlock() { textEdit()->setAlignment(Qt::AlignJustify); } void HtmlEditor::onFontSelectionChanged(const QFont &font) { // Change font family only textEdit()->setFontFamily(font.family()); InlineEditors::instance()->richTextFont->clearFocus(); // textEdit()->setFocus(); } void HtmlEditor::setBold(bool isChecked) { qWarning() << "setBold " << isChecked; textEdit()->setFontWeight(isChecked ? QFont::Bold : QFont::Normal); } HtmlEditor::~HtmlEditor() { // delete graphicsWidget()->widget(); } void HtmlEditor::autoSave(bool toFileToo) { m_htmlContent->setHtml(textEdit()->document()->toHtml("utf-8")); if (toFileToo) { m_htmlContent->saveToFile(); m_htmlContent->setEdited(); } } void HtmlEditor::validate() { if (Tools::htmlToText(textEdit()->toHtml()).isEmpty()) setEmpty(); QString convert = textEdit()->document()->toHtml("utf-8"); if (note()->allowCrossReferences()) convert = Tools::tagCrossReferences(convert, /*userLink=*/true); m_htmlContent->setHtml(convert); m_htmlContent->saveToFile(); m_htmlContent->setEdited(); disconnect(); graphicsWidget()->disconnect(); if (InlineEditors::instance()) { InlineEditors::instance()->disableRichTextToolBar(); // if (InlineEditors::instance()->richTextToolBar()) // InlineEditors::instance()->richTextToolBar()->hide(); } if (graphicsWidget()) { note()->setZValue(1); delete graphicsWidget()->widget(); setInlineEditor(0); } } /** class ImageEditor: */ ImageEditor::ImageEditor(ImageContent *imageContent, QWidget *parent) : NoteEditor(imageContent) { int choice = KMessageBox::questionYesNoCancel(parent, i18n("Images can not be edited here at the moment (the next version of BasKet Note Pads will include an image editor).\n" "Do you want to open it with an application that understand it?"), i18n("Edit Image Note"), KStandardGuiItem::open(), KGuiItem(i18n("Load From &File..."), IconNames::DOCUMENT_IMPORT), KStandardGuiItem::cancel()); switch (choice) { case (KMessageBox::Yes): note()->basket()->noteOpen(note()); break; case (KMessageBox::No): // Load from file cancel(); Global::bnpView->insertWizard(3); // 3 maps to m_actLoadFile break; case (KMessageBox::Cancel): cancel(); break; } } /** class AnimationEditor: */ AnimationEditor::AnimationEditor(AnimationContent *animationContent, QWidget *parent) : NoteEditor(animationContent) { int choice = KMessageBox::questionYesNo(parent, i18n("This animated image can not be edited here.\n" "Do you want to open it with an application that understands it?"), i18n("Edit Animation Note"), KStandardGuiItem::open(), KStandardGuiItem::cancel()); if (choice == KMessageBox::Yes) note()->basket()->noteOpen(note()); } /** class FileEditor: */ FileEditor::FileEditor(FileContent *fileContent, QWidget *parent) : NoteEditor(fileContent) , m_fileContent(fileContent) { QLineEdit *lineEdit = new QLineEdit(parent); FocusWidgetFilter *filter = new FocusWidgetFilter(lineEdit); QPalette palette; palette.setColor(lineEdit->backgroundRole(), note()->backgroundColor()); palette.setColor(lineEdit->foregroundRole(), note()->textColor()); lineEdit->setPalette(palette); lineEdit->setFont(note()->font()); lineEdit->setText(m_fileContent->fileName()); lineEdit->selectAll(); setInlineEditor(lineEdit); connect(filter, SIGNAL(returnPressed()), this, SIGNAL(askValidation())); connect(filter, SIGNAL(escapePressed()), this, SIGNAL(askValidation())); connect(filter, SIGNAL(mouseEntered()), this, SIGNAL(mouseEnteredEditorWidget())); } FileEditor::~FileEditor() { delete graphicsWidget()->widget(); } void FileEditor::autoSave(bool toFileToo) { // FIXME: How to detect cancel? if (toFileToo && !lineEdit()->text().isEmpty() && m_fileContent->trySetFileName(lineEdit()->text())) { m_fileContent->setFileName(lineEdit()->text()); m_fileContent->setEdited(); } } void FileEditor::validate() { autoSave(/*toFileToo=*/true); } /** class LinkEditor: */ LinkEditor::LinkEditor(LinkContent *linkContent, QWidget *parent) : NoteEditor(linkContent) { QPointer dialog = new LinkEditDialog(linkContent, parent); if (dialog->exec() == QDialog::Rejected) cancel(); if (linkContent->url().isEmpty() && linkContent->title().isEmpty()) setEmpty(); } /** class CrossReferenceEditor: */ CrossReferenceEditor::CrossReferenceEditor(CrossReferenceContent *crossReferenceContent, QWidget *parent) : NoteEditor(crossReferenceContent) { QPointer dialog = new CrossReferenceEditDialog(crossReferenceContent, parent); if (dialog->exec() == QDialog::Rejected) cancel(); if (crossReferenceContent->url().isEmpty() && crossReferenceContent->title().isEmpty()) setEmpty(); } /** class LauncherEditor: */ LauncherEditor::LauncherEditor(LauncherContent *launcherContent, QWidget *parent) : NoteEditor(launcherContent) { QPointer dialog = new LauncherEditDialog(launcherContent, parent); if (dialog->exec() == QDialog::Rejected) cancel(); if (launcherContent->name().isEmpty() && launcherContent->exec().isEmpty()) setEmpty(); } /** class ColorEditor: */ ColorEditor::ColorEditor(ColorContent *colorContent, QWidget *parent) : NoteEditor(colorContent) { QPointer dialog = new QColorDialog(parent); dialog->setCurrentColor(colorContent->color()); dialog->setWindowTitle(i18n("Edit Color Note")); // dialog->setButtons(QDialog::Ok | QDialog::Cancel); if (dialog->exec() == QDialog::Accepted) { if (dialog->currentColor() != colorContent->color()) { colorContent->setColor(dialog->currentColor()); colorContent->setEdited(); } } else cancel(); /* This code don't allow to set a caption to the dialog: QColor color = colorContent()->color(); color = QColorDialog::getColor(parent)==QDialog::Accepted&&color!=m_color); if ( color.isValid() ) { colorContent()->setColor(color); setEdited(); }*/ } /** class UnknownEditor: */ UnknownEditor::UnknownEditor(UnknownContent *unknownContent, QWidget *parent) : NoteEditor(unknownContent) { KMessageBox::information(parent, i18n("The type of this note is unknown and can not be edited here.\n" "You however can drag or copy the note into an application that understands it."), i18n("Edit Unknown Note")); } /*********************************************************************/ /** class LinkEditDialog: */ LinkEditDialog::LinkEditDialog(LinkContent *contentNote, QWidget *parent /*, QKeyEvent *ke*/) : QDialog(parent) , m_noteContent(contentNote) { // QDialog options setWindowTitle(i18n("Edit Link Note")); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); setObjectName("EditLink"); setModal(true); QWidget *page = new QWidget(this); mainLayout->addWidget(page); // QGridLayout *layout = new QGridLayout(page, /*nRows=*/4, /*nCols=*/2, /*margin=*/0, spacingHint()); QGridLayout *layout = new QGridLayout(page); mainLayout->addLayout(layout); QWidget *wid1 = new QWidget(page); mainLayout->addWidget(wid1); QHBoxLayout *titleLay = new QHBoxLayout(wid1); m_title = new QLineEdit(m_noteContent->title(), wid1); m_autoTitle = new QPushButton(i18n("Auto"), wid1); m_autoTitle->setCheckable(true); m_autoTitle->setChecked(m_noteContent->autoTitle()); titleLay->addWidget(m_title); titleLay->addWidget(m_autoTitle); QWidget *wid = new QWidget(page); mainLayout->addWidget(wid); QHBoxLayout *hLay = new QHBoxLayout(wid); m_icon = new KIconButton(wid); QLabel *label3 = new QLabel(page); mainLayout->addWidget(label3); label3->setText(i18n("&Icon:")); label3->setBuddy(m_icon); if (m_noteContent->url().isEmpty()) { m_url = new KUrlRequester(QUrl(""), wid); m_url->setMode(KFile::File | KFile::ExistingOnly); } else { m_url = new KUrlRequester(m_noteContent->url().toDisplayString(), wid); m_url->setMode(KFile::File | KFile::ExistingOnly); } if (m_noteContent->title().isEmpty()) { m_title->setText(""); } else { m_title->setText(m_noteContent->title()); } QUrl filteredURL = NoteFactory::filteredURL(QUrl::fromUserInput(m_url->lineEdit()->text())); // KURIFilter::self()->filteredURI(KUrl(m_url->lineEdit()->text())); m_icon->setIconType(KIconLoader::NoGroup, KIconLoader::MimeType); m_icon->setIconSize(LinkLook::lookForURL(filteredURL)->iconSize()); m_autoIcon = new QPushButton(i18n("Auto"), wid); // Create before to know size here: /* Icon button: */ m_icon->setIcon(m_noteContent->icon()); int minSize = m_autoIcon->sizeHint().height(); // Make the icon button at least the same height than the other buttons for a better alignment (nicer to the eyes): if (m_icon->sizeHint().height() < minSize) m_icon->setFixedSize(minSize, minSize); else m_icon->setFixedSize(m_icon->sizeHint().height(), m_icon->sizeHint().height()); // Make it square /* Auto button: */ m_autoIcon->setCheckable(true); m_autoIcon->setChecked(m_noteContent->autoIcon()); hLay->addWidget(m_icon); hLay->addWidget(m_autoIcon); hLay->addStretch(); m_url->lineEdit()->setMinimumWidth(m_url->lineEdit()->fontMetrics().maxWidth() * 20); m_title->setMinimumWidth(m_title->fontMetrics().maxWidth() * 20); // m_url->setShowLocalProtocol(true); QLabel *label1 = new QLabel(page); mainLayout->addWidget(label1); label1->setText(i18n("Ta&rget:")); label1->setBuddy(m_url); QLabel *label2 = new QLabel(page); mainLayout->addWidget(label2); label2->setText(i18n("&Title:")); label2->setBuddy(m_title); layout->addWidget(label1, 0, 0, Qt::AlignVCenter); layout->addWidget(label2, 1, 0, Qt::AlignVCenter); layout->addWidget(label3, 2, 0, Qt::AlignVCenter); layout->addWidget(m_url, 0, 1, Qt::AlignVCenter); layout->addWidget(wid1, 1, 1, Qt::AlignVCenter); layout->addWidget(wid, 2, 1, Qt::AlignVCenter); m_isAutoModified = false; connect(m_url, SIGNAL(textChanged(const QString &)), this, SLOT(urlChanged(const QString &))); connect(m_title, SIGNAL(textChanged(const QString &)), this, SLOT(doNotAutoTitle(const QString &))); connect(m_icon, SIGNAL(iconChanged(QString)), this, SLOT(doNotAutoIcon(QString))); connect(m_autoTitle, SIGNAL(clicked()), this, SLOT(guessTitle())); connect(m_autoIcon, SIGNAL(clicked()), this, SLOT(guessIcon())); QWidget *stretchWidget = new QWidget(page); mainLayout->addWidget(stretchWidget); QSizePolicy policy(QSizePolicy::Fixed, QSizePolicy::Expanding); policy.setHorizontalStretch(1); policy.setVerticalStretch(255); stretchWidget->setSizePolicy(policy); // Make it fill ALL vertical space layout->addWidget(stretchWidget, 3, 1, Qt::AlignVCenter); 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(okButton, SIGNAL(clicked()), SLOT(slotOk())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); // urlChanged(""); // if (ke) // qApp->postEvent(m_url->lineEdit(), ke); } LinkEditDialog::~LinkEditDialog() { } void LinkEditDialog::ensurePolished() { QDialog::ensurePolished(); if (m_url->lineEdit()->text().isEmpty()) { m_url->setFocus(); m_url->lineEdit()->end(false); } else { m_title->setFocus(); m_title->end(false); } } void LinkEditDialog::urlChanged(const QString &) { m_isAutoModified = true; // guessTitle(); // guessIcon(); // Optimization (filter only once): QUrl filteredURL = NoteFactory::filteredURL(m_url->url()); // KURIFilter::self()->filteredURI(KUrl(m_url->url())); if (m_autoIcon->isChecked()) m_icon->setIcon(NoteFactory::iconForURL(filteredURL)); if (m_autoTitle->isChecked()) { m_title->setText(NoteFactory::titleForURL(filteredURL)); m_autoTitle->setChecked(true); // Because the setText() will disable it! } } void LinkEditDialog::doNotAutoTitle(const QString &) { if (m_isAutoModified) m_isAutoModified = false; else m_autoTitle->setChecked(false); } void LinkEditDialog::doNotAutoIcon(QString) { m_autoIcon->setChecked(false); } void LinkEditDialog::guessIcon() { if (m_autoIcon->isChecked()) { QUrl filteredURL = NoteFactory::filteredURL(m_url->url()); // KURIFilter::self()->filteredURI(KUrl(m_url->url())); m_icon->setIcon(NoteFactory::iconForURL(filteredURL)); } } void LinkEditDialog::guessTitle() { if (m_autoTitle->isChecked()) { QUrl filteredURL = NoteFactory::filteredURL(m_url->url()); // KURIFilter::self()->filteredURI(KUrl(m_url->url())); m_title->setText(NoteFactory::titleForURL(filteredURL)); m_autoTitle->setChecked(true); // Because the setText() will disable it! } } void LinkEditDialog::slotOk() { QUrl filteredURL = NoteFactory::filteredURL(m_url->url()); // KURIFilter::self()->filteredURI(KUrl(m_url->url())); m_noteContent->setLink(filteredURL, m_title->text(), m_icon->icon(), m_autoTitle->isChecked(), m_autoIcon->isChecked()); m_noteContent->setEdited(); /* Change icon size if link look have changed */ LinkLook *linkLook = LinkLook::lookForURL(filteredURL); QString icon = m_icon->icon(); // When we change size, icon isn't changed and keep it's old size m_icon->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); // Reset size policy m_icon->setIconSize(linkLook->iconSize()); // So I store it's name and reload it after size change ! m_icon->setIcon(icon); int minSize = m_autoIcon->sizeHint().height(); // Make the icon button at least the same height than the other buttons for a better alignment (nicer to the eyes): if (m_icon->sizeHint().height() < minSize) m_icon->setFixedSize(minSize, minSize); else m_icon->setFixedSize(m_icon->sizeHint().height(), m_icon->sizeHint().height()); // Make it square } /** class CrossReferenceEditDialog: */ CrossReferenceEditDialog::CrossReferenceEditDialog(CrossReferenceContent *contentNote, QWidget *parent /*, QKeyEvent *ke*/) : QDialog(parent) , m_noteContent(contentNote) { QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); // QDialog options setWindowTitle(i18n("Edit Cross Reference")); QWidget *page = new QWidget(this); mainLayout->addWidget(page); QWidget *wid = new QWidget(page); mainLayout->addWidget(wid); QGridLayout *layout = new QGridLayout(page); mainLayout->addLayout(layout); m_targetBasket = new KComboBox(wid); this->generateBasketList(m_targetBasket); if (m_noteContent->url().isEmpty()) { BasketListViewItem *item = Global::bnpView->topLevelItem(0); m_noteContent->setCrossReference(QUrl::fromUserInput(item->data(0, Qt::UserRole).toString()), m_targetBasket->currentText(), "edit-copy"); this->urlChanged(0); } else { QString url = m_noteContent->url().url(); // cannot use findData because I'm using a StringList and I don't have the second // piece of data to make find work. for (int i = 0; i < m_targetBasket->count(); ++i) { if (url == m_targetBasket->itemData(i, Qt::UserRole).toStringList().first()) { m_targetBasket->setCurrentIndex(i); break; } } } QLabel *label1 = new QLabel(page); mainLayout->addWidget(label1); label1->setText(i18n("Ta&rget:")); label1->setBuddy(m_targetBasket); layout->addWidget(label1, 0, 0, Qt::AlignVCenter); layout->addWidget(m_targetBasket, 0, 1, Qt::AlignVCenter); connect(m_targetBasket, SIGNAL(activated(int)), this, SLOT(urlChanged(int))); QWidget *stretchWidget = new QWidget(page); mainLayout->addWidget(stretchWidget); QSizePolicy policy(QSizePolicy::Fixed, QSizePolicy::Expanding); policy.setHorizontalStretch(1); policy.setVerticalStretch(255); stretchWidget->setSizePolicy(policy); // Make it fill ALL vertical space layout->addWidget(stretchWidget, 3, 1, Qt::AlignVCenter); 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, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); setObjectName("EditCrossReference"); setModal(true); connect(okButton, SIGNAL(clicked()), SLOT(slotOk())); } CrossReferenceEditDialog::~CrossReferenceEditDialog() { } void CrossReferenceEditDialog::urlChanged(const int index) { if (m_targetBasket) m_noteContent->setCrossReference( QUrl::fromUserInput(m_targetBasket->itemData(index, Qt::UserRole).toStringList().first()), m_targetBasket->currentText().trimmed(), m_targetBasket->itemData(index, Qt::UserRole).toStringList().last()); } void CrossReferenceEditDialog::slotOk() { m_noteContent->setEdited(); } void CrossReferenceEditDialog::generateBasketList(KComboBox *targetList, BasketListViewItem *item, int indent) { if (!item) { // include ALL top level items and their children. for (int i = 0; i < Global::bnpView->topLevelItemCount(); ++i) this->generateBasketList(targetList, Global::bnpView->topLevelItem(i)); } else { BasketScene *bv = item->basket(); // TODO: add some fancy deco stuff to make it look like a tree list. QString pad; QString text = item->text(0); // user text text.prepend(pad.fill(' ', indent * 2)); // create the link text QString link = "basket://"; link.append(bv->folderName().toLower()); // unique ref. QStringList data; data.append(link); data.append(bv->icon()); targetList->addItem(item->icon(0), text, QVariant(data)); int subBasketCount = item->childCount(); if (subBasketCount > 0) { indent++; for (int i = 0; i < subBasketCount; ++i) { this->generateBasketList(targetList, (BasketListViewItem *)item->child(i), indent); } } } } /** class LauncherEditDialog: */ LauncherEditDialog::LauncherEditDialog(LauncherContent *contentNote, QWidget *parent) : QDialog(parent) , m_noteContent(contentNote) { QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); // QDialog options setWindowTitle(i18n("Edit Launcher Note")); setObjectName("EditLauncher"); setModal(true); QWidget *page = new QWidget(this); mainLayout->addWidget(page); // QGridLayout *layout = new QGridLayout(page, /*nRows=*/4, /*nCols=*/2, /*margin=*/0, spacingHint()); QGridLayout *layout = new QGridLayout(page); mainLayout->addLayout(layout); KService service(contentNote->fullPath()); m_command = new RunCommandRequester(service.exec(), i18n("Choose a command to run:"), page); mainLayout->addWidget(m_command); m_name = new QLineEdit(service.name(), page); mainLayout->addWidget(m_name); QWidget *wid = new QWidget(page); mainLayout->addWidget(wid); QHBoxLayout *hLay = new QHBoxLayout(wid); m_icon = new KIconButton(wid); QLabel *label = new QLabel(page); mainLayout->addWidget(label); label->setText(i18n("&Icon:")); label->setBuddy(m_icon); m_icon->setIconType(KIconLoader::NoGroup, KIconLoader::Application); m_icon->setIconSize(LinkLook::launcherLook->iconSize()); QPushButton *guessButton = new QPushButton(i18n("&Guess"), wid); /* Icon button: */ m_icon->setIcon(service.icon()); int minSize = guessButton->sizeHint().height(); // Make the icon button at least the same height than the other buttons for a better alignment (nicer to the eyes): if (m_icon->sizeHint().height() < minSize) m_icon->setFixedSize(minSize, minSize); else m_icon->setFixedSize(m_icon->sizeHint().height(), m_icon->sizeHint().height()); // Make it square /* Guess button: */ hLay->addWidget(m_icon); hLay->addWidget(guessButton); hLay->addStretch(); m_command->lineEdit()->setMinimumWidth(m_command->lineEdit()->fontMetrics().maxWidth() * 20); QLabel *label1 = new QLabel(page); mainLayout->addWidget(label1); label1->setText(i18n("Comman&d:")); label1->setBuddy(m_command->lineEdit()); QLabel *label2 = new QLabel(page); mainLayout->addWidget(label2); label2->setText(i18n("&Name:")); label2->setBuddy(m_name); layout->addWidget(label1, 0, 0, Qt::AlignVCenter); layout->addWidget(label2, 1, 0, Qt::AlignVCenter); layout->addWidget(label, 2, 0, Qt::AlignVCenter); layout->addWidget(m_command, 0, 1, Qt::AlignVCenter); layout->addWidget(m_name, 1, 1, Qt::AlignVCenter); layout->addWidget(wid, 2, 1, Qt::AlignVCenter); QWidget *stretchWidget = new QWidget(page); mainLayout->addWidget(stretchWidget); 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(okButton, SIGNAL(clicked()), SLOT(slotOk())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); QSizePolicy policy(QSizePolicy::Fixed, QSizePolicy::Expanding); policy.setHorizontalStretch(1); policy.setVerticalStretch(255); stretchWidget->setSizePolicy(policy); // Make it fill ALL vertical space layout->addWidget(stretchWidget, 3, 1, Qt::AlignVCenter); connect(guessButton, SIGNAL(clicked()), this, SLOT(guessIcon())); } LauncherEditDialog::~LauncherEditDialog() { } void LauncherEditDialog::ensurePolished() { QDialog::ensurePolished(); if (m_command->runCommand().isEmpty()) { m_command->lineEdit()->setFocus(); m_command->lineEdit()->end(false); } else { m_name->setFocus(); m_name->end(false); } } void LauncherEditDialog::slotOk() { // TODO: Remember if a string has been modified AND IS DIFFERENT FROM THE // ORIGINAL! KDesktopFile dtFile(m_noteContent->fullPath()); KConfigGroup grp = dtFile.desktopGroup(); grp.writeEntry("Exec", m_command->runCommand()); grp.writeEntry("Name", m_name->text()); grp.writeEntry("Icon", m_icon->icon()); // Just for faster feedback: conf object will save to disk (and then // m_note->loadContent() called) m_noteContent->setLauncher(m_name->text(), m_icon->icon(), m_command->runCommand()); m_noteContent->setEdited(); } void LauncherEditDialog::guessIcon() { m_icon->setIcon(NoteFactory::iconForCommand(m_command->runCommand())); } /** class InlineEditors: */ InlineEditors::InlineEditors() { } InlineEditors::~InlineEditors() { } InlineEditors *InlineEditors::instance() { static InlineEditors *instance = 0; if (!instance) instance = new InlineEditors(); return instance; } void InlineEditors::initToolBars(KActionCollection *ac) { QFont defaultFont; QColor textColor = (Global::bnpView && Global::bnpView->currentBasket() ? Global::bnpView->currentBasket()->textColor() : palette().color(QPalette::Text)); // NOTE: currently it is NULL since initToolBars is called early. Could use different way to get MainWindow pointer from main KMainWindow *parent = Global::activeMainWindow(); // Init the RichTextEditor Toolbar: richTextFont = new QFontComboBox(Global::activeMainWindow()); focusWidgetFilter = new FocusWidgetFilter(richTextFont); richTextFont->setFixedWidth(richTextFont->sizeHint().width() * 2 / 3); richTextFont->setCurrentFont(defaultFont.family()); QWidgetAction *action = new QWidgetAction(parent); ac->addAction("richtext_font", action); action->setDefaultWidget(richTextFont); action->setText(i18n("Font")); ac->setDefaultShortcut(action, Qt::Key_F6); richTextFontSize = new FontSizeCombo(/*rw=*/true, Global::activeMainWindow()); richTextFontSize->setFontSize(defaultFont.pointSize()); action = new QWidgetAction(parent); ac->addAction("richtext_font_size", action); action->setDefaultWidget(richTextFontSize); action->setText(i18n("Font Size")); ac->setDefaultShortcut(action, Qt::Key_F7); richTextColor = new KColorCombo(Global::activeMainWindow()); richTextColor->installEventFilter(focusWidgetFilter); richTextColor->setFixedWidth(richTextColor->sizeHint().height() * 2); richTextColor->setColor(textColor); action = new QWidgetAction(parent); ac->addAction("richtext_color", action); action->setDefaultWidget(richTextColor); action->setText(i18n("Color")); KToggleAction *ta = NULL; ta = new KToggleAction(ac); ac->addAction("richtext_bold", ta); ta->setText(i18n("Bold")); ta->setIcon(QIcon::fromTheme("format-text-bold")); ac->setDefaultShortcut(ta, QKeySequence("Ctrl+B")); richTextBold = ta; ta = new KToggleAction(ac); ac->addAction("richtext_italic", ta); ta->setText(i18n("Italic")); ta->setIcon(QIcon::fromTheme("format-text-italic")); ac->setDefaultShortcut(ta, QKeySequence("Ctrl+I")); richTextItalic = ta; ta = new KToggleAction(ac); ac->addAction("richtext_underline", ta); ta->setText(i18n("Underline")); ta->setIcon(QIcon::fromTheme("format-text-underline")); ac->setDefaultShortcut(ta, QKeySequence("Ctrl+U")); richTextUnderline = ta; #if 0 ta = new KToggleAction(ac); ac->addAction("richtext_super", ta); ta->setText(i18n("Superscript")); ta->setIcon(QIcon::fromTheme("text_super")); richTextSuper = ta; ta = new KToggleAction(ac); ac->addAction("richtext_sub", ta); ta->setText(i18n("Subscript")); ta->setIcon(QIcon::fromTheme("text_sub")); richTextSub = ta; #endif ta = new KToggleAction(ac); ac->addAction("richtext_left", ta); ta->setText(i18n("Align Left")); ta->setIcon(QIcon::fromTheme("format-justify-left")); richTextLeft = ta; ta = new KToggleAction(ac); ac->addAction("richtext_center", ta); ta->setText(i18n("Centered")); ta->setIcon(QIcon::fromTheme("format-justify-center")); richTextCenter = ta; ta = new KToggleAction(ac); ac->addAction("richtext_right", ta); ta->setText(i18n("Align Right")); ta->setIcon(QIcon::fromTheme("format-justify-right")); richTextRight = ta; ta = new KToggleAction(ac); ac->addAction("richtext_block", ta); ta->setText(i18n("Justified")); ta->setIcon(QIcon::fromTheme("format-justify-fill")); richTextJustified = ta; QActionGroup *alignmentGroup = new QActionGroup(this); alignmentGroup->addAction(richTextLeft); alignmentGroup->addAction(richTextCenter); alignmentGroup->addAction(richTextRight); alignmentGroup->addAction(richTextJustified); ta = new KToggleAction(ac); ac->addAction("richtext_undo", ta); ta->setText(i18n("Undo")); ta->setIcon(QIcon::fromTheme("edit-undo")); richTextUndo = ta; ta = new KToggleAction(ac); ac->addAction("richtext_redo", ta); ta->setText(i18n("Redo")); ta->setIcon(QIcon::fromTheme("edit-redo")); richTextRedo = ta; disableRichTextToolBar(); } KToolBar *InlineEditors::richTextToolBar() { if (Global::activeMainWindow()) { Global::activeMainWindow()->toolBar(); // Make sure we create the main toolbar FIRST, so it will be on top of the edit toolbar! return Global::activeMainWindow()->toolBar("richTextEditToolBar"); } else return 0; } void InlineEditors::enableRichTextToolBar() { richTextFont->setEnabled(true); richTextFontSize->setEnabled(true); richTextColor->setEnabled(true); richTextBold->setEnabled(true); richTextItalic->setEnabled(true); richTextUnderline->setEnabled(true); richTextLeft->setEnabled(true); richTextCenter->setEnabled(true); richTextRight->setEnabled(true); richTextJustified->setEnabled(true); richTextUndo->setEnabled(true); richTextRedo->setEnabled(true); } void InlineEditors::disableRichTextToolBar() { disconnect(richTextFont); disconnect(richTextFontSize); disconnect(richTextColor); disconnect(richTextBold); disconnect(richTextItalic); disconnect(richTextUnderline); disconnect(richTextLeft); disconnect(richTextCenter); disconnect(richTextRight); disconnect(richTextJustified); disconnect(richTextUndo); disconnect(richTextRedo); richTextFont->setEnabled(false); richTextFontSize->setEnabled(false); richTextColor->setEnabled(false); richTextBold->setEnabled(false); richTextItalic->setEnabled(false); richTextUnderline->setEnabled(false); richTextLeft->setEnabled(false); richTextCenter->setEnabled(false); richTextRight->setEnabled(false); richTextJustified->setEnabled(false); richTextUndo->setEnabled(false); richTextRedo->setEnabled(false); // Return to a "proper" state: QFont defaultFont; QColor textColor = (Global::bnpView && Global::bnpView->currentBasket() ? Global::bnpView->currentBasket()->textColor() : palette().color(QPalette::Text)); richTextFont->setCurrentFont(defaultFont.family()); richTextFontSize->setFontSize(defaultFont.pointSize()); richTextColor->setColor(textColor); richTextBold->setChecked(false); richTextItalic->setChecked(false); richTextUnderline->setChecked(false); richTextLeft->setChecked(false); richTextCenter->setChecked(false); richTextRight->setChecked(false); richTextJustified->setChecked(false); } QPalette InlineEditors::palette() const { return qApp->palette(); } diff --git a/src/noteedit.h b/src/noteedit.h index 506bc11..c6cf2bc 100644 --- a/src/noteedit.h +++ b/src/noteedit.h @@ -1,355 +1,342 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef NOTEEDIT_H #define NOTEEDIT_H #include #include #include #include "notecontent.h" class QGraphicsProxyWidget; class QWidget; class QPushButton; class QKeyEvent; class QFontComboBox; class QTextCharFormat; class QAction; class KIconButton; class KUrlRequester; class KTextEdit; class KToggleAction; class KToolBar; class KActionCollection; class KComboBox; class KColorCombo; class FontSizeCombo; class Note; class RunCommandRequester; class FocusWidgetFilter; class BasketListViewItem; /** The base class for every note editor. * Scenario: * The Basket class calls NoteEditor::editNoteContent() with the NoteContent to edit. * This method create the good child NoteEditor depending * on the note content type and return it to the Basket. * This custom NoteEditor have two choices regarding what to do in its constructor: * - Display a dialog and then call cancel() if the user canceled the dialog; * - Create an inline editor and call setInlineEditor() with that editor as parameter. * When the user exit the edition, validate() is called by the Basket. * You should then call setEmpty() is the user cleared the content. * The custom editor SHOULD call the NoteEditor constructor. * If the user cleared the content OR if the user canceled the dialog whereas he/she * JUST ADDED the note, then the note will be deleted by the Basket. */ class NoteEditor : public QObject { Q_OBJECT public: explicit NoteEditor(NoteContent *noteContent); ~NoteEditor() override; bool isEmpty() { return m_isEmpty; } bool canceled() { return m_canceled; } bool isInline() { return m_widget != 0; } QGraphicsProxyWidget *graphicsWidget() { return m_widget; } KTextEdit *textEdit() { return m_textEdit; } QLineEdit *lineEdit() { return m_lineEdit; } void setCursorTo(const QPointF &pos); void connectActions(BasketScene *scene); void startSelection(const QPointF &pos); void updateSelection(const QPointF &pos); void endSelection(const QPointF &pos); void paste(const QPointF &pos, QClipboard::Mode mode); private: bool m_isEmpty; bool m_canceled; QGraphicsProxyWidget *m_widget; KTextEdit *m_textEdit; QLineEdit *m_lineEdit; NoteContent *m_noteContent; public: NoteContent *noteContent() { return m_noteContent; } Note *note(); protected: void setEmpty() { m_isEmpty = true; } void cancel() { m_canceled = true; } void setInlineEditor(QWidget *inlineEditor); public: virtual void validate() { } virtual void autoSave(bool /*toFileToo*/) { } // Same as validate(), but does not precede editor close and is triggered either while the editor widget changed size or after 3 seconds of inactivity. signals: void askValidation(); void mouseEnteredEditorWidget(); public: static NoteEditor *editNoteContent(NoteContent *noteContent, QWidget *parent); }; class TextEditor : public NoteEditor { Q_OBJECT public: TextEditor(TextContent *textContent, QWidget *parent); ~TextEditor() override; void validate() override; void autoSave(bool toFileToo) override; protected: TextContent *m_textContent; }; class HtmlEditor : public NoteEditor { Q_OBJECT public: HtmlEditor(HtmlContent *htmlContent, QWidget *parent); ~HtmlEditor() override; void validate() override; void autoSave(bool toFileToo) override; protected: HtmlContent *m_htmlContent; public slots: void cursorPositionChanged(); void editTextChanged(); void charFormatChanged(const QTextCharFormat &format); protected slots: void setBold(bool isChecked); void setLeft(); void setCentered(); void setRight(); void setBlock(); void onFontSelectionChanged(const QFont &font); //!< When a font is selected from combo box }; class ImageEditor : public NoteEditor { Q_OBJECT public: ImageEditor(ImageContent *imageContent, QWidget *parent); }; class AnimationEditor : public NoteEditor { Q_OBJECT public: AnimationEditor(AnimationContent *animationContent, QWidget *parent); }; class FileEditor : public NoteEditor { Q_OBJECT public: FileEditor(FileContent *fileContent, QWidget *parent); ~FileEditor() override; void validate() override; void autoSave(bool toFileToo) override; protected: FileContent *m_fileContent; }; class LinkEditor : public NoteEditor { Q_OBJECT public: LinkEditor(LinkContent *linkContent, QWidget *parent); }; class CrossReferenceEditor : public NoteEditor { Q_OBJECT public: CrossReferenceEditor(CrossReferenceContent *crossReferenceContent, QWidget *parent); }; class LauncherEditor : public NoteEditor { Q_OBJECT public: LauncherEditor(LauncherContent *launcherContent, QWidget *parent); }; class ColorEditor : public NoteEditor { Q_OBJECT public: ColorEditor(ColorContent *colorContent, QWidget *parent); }; class UnknownEditor : public NoteEditor { Q_OBJECT public: UnknownEditor(UnknownContent *unknownContent, QWidget *parent); }; /** The dialog to edit Link Note content. * @author Sébastien Laoût */ class LinkEditDialog : public QDialog { Q_OBJECT public: explicit LinkEditDialog(LinkContent *contentNote, QWidget *parent = nullptr); ~LinkEditDialog() override; void ensurePolished(); protected slots: void slotOk(); void urlChanged(const QString &); void doNotAutoTitle(const QString &); void doNotAutoIcon(QString); void guessTitle(); void guessIcon(); private: LinkContent *m_noteContent; bool m_isAutoModified; KUrlRequester *m_url; QLineEdit *m_title; KIconButton *m_icon; QPushButton *m_autoTitle; QPushButton *m_autoIcon; }; /** The dialog to edit cross reference content. * @author Brian C. Milco */ class CrossReferenceEditDialog : public QDialog { Q_OBJECT public: explicit CrossReferenceEditDialog(CrossReferenceContent *contentNote, QWidget *parent = nullptr); ~CrossReferenceEditDialog() override; protected slots: void slotOk(); void urlChanged(const int index); protected: void generateBasketList(KComboBox *targetList, BasketListViewItem *item = nullptr, int indent = 0); private: CrossReferenceContent *m_noteContent; KComboBox *m_targetBasket; }; /** The dialog to edit Launcher Note content. * @author Sébastien Laoût */ class LauncherEditDialog : public QDialog { Q_OBJECT public: explicit LauncherEditDialog(LauncherContent *contentNote, QWidget *parent = nullptr); ~LauncherEditDialog() override; void ensurePolished(); protected slots: void slotOk(); void guessIcon(); private: LauncherContent *m_noteContent; RunCommandRequester *m_command; QLineEdit *m_name; KIconButton *m_icon; }; /** This class manage toolbars for the inline editors. * The toolbars should be created once at the application startup, * then KXMLGUI can manage them and save their state and position... * @author Sébastien Laoût */ class InlineEditors : public QObject { Q_OBJECT public: InlineEditors(); ~InlineEditors() override; void initToolBars(KActionCollection *ac); static InlineEditors *instance(); public: // Rich Text ToolBar: KToolBar *richTextToolBar(); void enableRichTextToolBar(); void disableRichTextToolBar(); QPalette palette() const; QFontComboBox *richTextFont; FontSizeCombo *richTextFontSize; KColorCombo *richTextColor; KToggleAction *richTextBold; KToggleAction *richTextItalic; KToggleAction *richTextUnderline; // KToggleAction *richTextSuper; // KToggleAction *richTextSub; KToggleAction *richTextLeft; KToggleAction *richTextCenter; KToggleAction *richTextRight; KToggleAction *richTextJustified; QAction *richTextUndo; QAction *richTextRedo; FocusWidgetFilter *focusWidgetFilter; }; #endif // NOTEEDIT_H diff --git a/src/notefactory.cpp b/src/notefactory.cpp index b4af32e..3c26a24 100644 --- a/src/notefactory.cpp +++ b/src/notefactory.cpp @@ -1,1055 +1,1042 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "notefactory.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //For createHeuristicMask #include #include #include #include #include #include //For Qt::mightBeRichText(...) #include //For KGlobal::mainComponent().aboutData(...) #include #include #include #include #include #include #include #include #include "basketlistview.h" #include "basketscene.h" #include "file_mimetypes.h" #include "global.h" #include "note.h" #include "notedrag.h" #include "settings.h" #include "tools.h" #include "variouswidgets.h" //For IconSizeDialog #include "debugwindow.h" /** Create notes from scratch (just a content) */ Note *NoteFactory::createNoteText(const QString &text, BasketScene *parent, bool reallyPlainText /* = false*/) { Note *note = new Note(parent); if (reallyPlainText) { TextContent *content = new TextContent(note, createFileForNewNote(parent, "txt")); content->setText(text); content->saveToFile(); } else { HtmlContent *content = new HtmlContent(note, createFileForNewNote(parent, "html")); QString html = "" + Tools::textToHTMLWithoutP(text) + ""; content->setHtml(html); content->saveToFile(); } return note; } Note *NoteFactory::createNoteHtml(const QString &html, BasketScene *parent) { Note *note = new Note(parent); HtmlContent *content = new HtmlContent(note, createFileForNewNote(parent, "html")); content->setHtml(html); content->saveToFile(); return note; } Note *NoteFactory::createNoteLink(const QUrl &url, BasketScene *parent) { Note *note = new Note(parent); new LinkContent(note, url, titleForURL(url), iconForURL(url), /*autoTitle=*/true, /*autoIcon=*/true); return note; } Note *NoteFactory::createNoteLink(const QUrl &url, const QString &title, BasketScene *parent) { Note *note = new Note(parent); new LinkContent(note, url, title, iconForURL(url), /*autoTitle=*/false, /*autoIcon=*/true); return note; } Note *NoteFactory::createNoteCrossReference(const QUrl &url, BasketScene *parent) { Note *note = new Note(parent); new CrossReferenceContent(note, url, titleForURL(url), iconForURL(url)); return note; } Note *NoteFactory::createNoteCrossReference(const QUrl &url, const QString &title, BasketScene *parent) { Note *note = new Note(parent); new CrossReferenceContent(note, url, title, iconForURL(url)); return note; } Note *NoteFactory::createNoteCrossReference(const QUrl &url, const QString &title, const QString &icon, BasketScene *parent) { Note *note = new Note(parent); new CrossReferenceContent(note, url, title, icon); return note; } Note *NoteFactory::createNoteImage(const QPixmap &image, BasketScene *parent) { Note *note = new Note(parent); ImageContent *content = new ImageContent(note, createFileForNewNote(parent, "png")); content->setPixmap(image); content->saveToFile(); return note; } Note *NoteFactory::createNoteColor(const QColor &color, BasketScene *parent) { Note *note = new Note(parent); new ColorContent(note, color); return note; } /** Return a string list containing {url1, title1, url2, title2, url3, title3...} */ QStringList NoteFactory::textToURLList(const QString &text) { // List to return: QStringList list; // Split lines: QStringList texts = text.split('\n'); // For each lines: QStringList::iterator it; for (it = texts.begin(); it != texts.end(); ++it) { // Strip white spaces: (*it) = (*it).trimmed(); // Don't care of empty entries: if ((*it).isEmpty()) continue; // Compute lower case equivalent: QString ltext = (*it).toLower(); /* Search for mail address ("*@*.*" ; "*" can contain '_', '-', or '.') and add protocol to it */ QString mailExpString = "[\\w-\\.]+@[\\w-\\.]+\\.[\\w]+"; QRegExp mailExp("^" + mailExpString + '$'); if (mailExp.exactMatch(ltext)) { ltext.insert(0, "mailto:"); (*it).insert(0, "mailto:"); } // TODO: Recognize "" (link between '<' and '>') // TODO: Replace " at " by "@" and " dot " by "." to look for e-mail addresses /* Search for mail address like "Name " */ QRegExp namedMailExp("^([\\w\\s]+)\\s<(" + mailExpString + ")>$"); // namedMailExp.setCaseSensitive(true); // For the name to be keeped with uppercases // DOESN'T WORK ! if (namedMailExp.exactMatch(ltext)) { QString name = namedMailExp.cap(1); QString address = "mailto:" + namedMailExp.cap(2); // Threat it NOW, as it's an exception (it have a title): list.append(address); list.append(name); continue; } /* Search for an url and create an URL note */ if ((ltext.startsWith('/') && ltext[1] != '/' && ltext[1] != '*') || // Take files but not C/C++/... comments ! ltext.startsWith(QLatin1String("file:")) || ltext.startsWith(QLatin1String("http://")) || ltext.startsWith(QLatin1String("https://")) || ltext.startsWith(QLatin1String("www.")) || ltext.startsWith(QLatin1String("ftp.")) || ltext.startsWith(QLatin1String("ftp://")) || ltext.startsWith(QLatin1String("mailto:"))) { // First, correct the text to use the good format for the url if (ltext.startsWith('/')) (*it).insert(0, "file:"); if (ltext.startsWith(QLatin1String("www."))) (*it).insert(0, "http://"); if (ltext.startsWith(QLatin1String("ftp."))) (*it).insert(0, "ftp://"); // And create the Url note (or launcher if URL point a .desktop file) list.append(*it); list.append(""); // We don't have any title } else return QStringList(); // FAILED: treat the text as a text, and not as a URL list! } return list; } Note *NoteFactory::createNoteFromText(const QString &text, BasketScene *parent) { /* Search for a color (#RGB , #RRGGBB , #RRRGGGBBB , #RRRRGGGGBBBB) and create a color note */ QRegExp exp("^#(?:[a-fA-F\\d]{3}){1,4}$"); if (exp.exactMatch(text)) return createNoteColor(QColor(text), parent); /* Try to convert the text as a URL or a list of URLs */ QStringList uriList = textToURLList(text); if (!uriList.isEmpty()) { // TODO: This code is almost duplicated from fropURLs()! Note *note; Note *firstNote = 0; Note *lastInserted = 0; QStringList::iterator it; for (it = uriList.begin(); it != uriList.end(); ++it) { QString url = (*it); ++it; QString title = (*it); if (title.isEmpty()) note = createNoteLinkOrLauncher(QUrl::fromUserInput(url), parent); else note = createNoteLink(QUrl::fromUserInput(url), title, parent); // If we got a new note, insert it in a linked list (we will return the first note of that list): if (note) { // qDebug() << "Drop URL: " << (*it).toDisplayString(); if (!firstNote) firstNote = note; else { lastInserted->setNext(note); note->setPrev(lastInserted); } lastInserted = note; } } return firstNote; // It don't return ALL inserted notes ! } // QString newText = text.trimmed(); // The text for a new note, without useless spaces /* Else, it's a text or an HTML note, so, create it */ if (Qt::mightBeRichText(/*newT*/ text)) return createNoteHtml(/*newT*/ text, parent); else return createNoteText(/*newT*/ text, parent); } Note *NoteFactory::createNoteLauncher(const QUrl &url, BasketScene *parent) { if (url.isEmpty()) return createNoteLauncher("", "", "", parent); else return copyFileAndLoad(url, parent); } Note *NoteFactory::createNoteLauncher(const QString &command, const QString &name, const QString &icon, BasketScene *parent) { QString fileName = createNoteLauncherFile(command, name, icon, parent); if (fileName.isEmpty()) return 0L; else return loadFile(fileName, parent); } QString NoteFactory::createNoteLauncherFile(const QString &command, const QString &name, const QString &icon, BasketScene *parent) { QString content = QString( "[Desktop Entry]\n" "Exec=%1\n" "Name=%2\n" "Icon=%3\n" "Encoding=UTF-8\n" "Type=Application\n") .arg(command, name, icon.isEmpty() ? QString("exec") : icon); QString fileName = fileNameForNewNote(parent, "launcher.desktop"); QString fullPath = parent->fullPathForFileName(fileName); // parent->dontCareOfCreation(fullPath); QFile file(fullPath); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream.setCodec("UTF-8"); stream << content; file.close(); return fileName; } else return QString(); } Note *NoteFactory::createNoteLinkOrLauncher(const QUrl &url, BasketScene *parent) { // IMPORTANT: we create the service ONLY if the extension is ".desktop". // Otherwise, KService take a long time to analyze all the file // and output such things to stdout: // "Invalid entry (missing '=') at /my/file.ogg:11984" // "Invalid entry (missing ']') at /my/file.ogg:11984"... KService::Ptr service; if (url.fileName().endsWith(QLatin1String(".desktop"))) service = new KService(url.path()); // If link point to a .desktop file then add a launcher, otherwise it's a link if (service && service->isValid()) return createNoteLauncher(url, parent); else return createNoteLink(url, parent); } bool NoteFactory::movingNotesInTheSameBasket(const QMimeData *source, BasketScene *parent, Qt::DropAction action) { if (NoteDrag::canDecode(source)) return action == Qt::MoveAction && NoteDrag::basketOf(source) == parent; else return false; } Note *NoteFactory::dropNote(const QMimeData *source, BasketScene *parent, bool fromDrop, Qt::DropAction action, Note * /*noteSource*/) { if (source == 0) { return 0; } Note *note = 0L; QStringList formats = source->formats(); /* No data */ if (formats.size() == 0) { // TODO: add a parameter to say if it's from a clipboard paste, a selection paste, or a drop // To be able to say "The clipboard/selection/drop is empty". // KMessageBox::error(parent, i18n("There is no data to insert."), i18n("No Data")); return 0; } /* Debug */ if (Global::debugWindow) { *Global::debugWindow << "Drop :"; for (int i = 0; i < formats.size(); ++i) *Global::debugWindow << "\t[" + QString::number(i) + "] " + formats[i]; switch (action) { // The source want that we: case Qt::CopyAction: *Global::debugWindow << ">> Drop action: Copy"; break; case Qt::MoveAction: *Global::debugWindow << ">> Drop action: Move"; break; case Qt::LinkAction: *Global::debugWindow << ">> Drop action: Link"; break; default: *Global::debugWindow << ">> Drop action: Unknown"; // supported by Qt! } } /* Copy or move a Note */ if (NoteDrag::canDecode(source)) { bool moveFiles = fromDrop && action == Qt::MoveAction; bool moveNotes = moveFiles; return NoteDrag::decode(source, parent, moveFiles, moveNotes); // Filename will be kept } /* Else : Drop object to note */ QImage image = qvariant_cast(source->imageData()); if (!image.isNull()) return createNoteImage(QPixmap::fromImage(image), parent); if (source->hasColor()) { return createNoteColor(qvariant_cast(source->colorData()), parent); } // And then the hack (if provide color MIME type or a text that contains color), using createNote Color RegExp: QString hack; QRegExp exp("^#(?:[a-fA-F\\d]{3}){1,4}$"); hack = source->text(); if (source->hasFormat("application/x-color") || (!hack.isNull() && exp.exactMatch(hack))) { QColor color = qvariant_cast(source->colorData()); if (color.isValid()) return createNoteColor(color, parent); // if ( (note = createNoteColor(color, parent)) ) // return note; // // Theoretically it should be returned. If not, continue by dropping other things } QList urls = source->urls(); if (!urls.isEmpty()) { // If it's a Paste, we should know if files should be copied (copy&paste) or moved (cut&paste): if (!fromDrop && Tools::isAFileCut(source)) action = Qt::MoveAction; return dropURLs(urls, parent, action, fromDrop); } // FIXME: use dropURLs() also from Mozilla? /* * Mozilla's stuff sometimes uses utf-16-le - little-endian UTF-16. * * This has the property that for the ASCII subset case (And indeed, the * ISO-8859-1 subset, I think), if you treat it as a C-style string, * it'll come out to one character long in most cases, since it looks * like: * * "<\0H\0T\0M\0L\0>\0" * * A strlen() call on that will give you 1, which simply isn't correct. * That might, I suppose, be the answer, or something close. * * Also, Mozilla's drag/drop code predates the use of MIME types in XDnD * - hence it'll throw about STRING and UTF8_STRING quite happily, hence * the odd named types. * * Thanks to Dave Cridland for having said me that. */ if (source->hasFormat("text/x-moz-url")) { // FOR MOZILLA // Get the array and create a QChar array of 1/2 of the size QByteArray mozilla = source->data("text/x-moz-url"); QVector chars(mozilla.count() / 2); // A small debug work to know the value of each bytes if (Global::debugWindow) for (int i = 0; i < mozilla.count(); i++) *Global::debugWindow << QString("'") + QChar(mozilla[i]) + "' " + QString::number(int(mozilla[i])); // text/x-moz-url give the URL followed by the link title and separated by OxOA (10 decimal: new line?) uint size = 0; QChar *name = 0L; // For each little endian mozilla chars, copy it to the array of QChars for (int i = 0; i < mozilla.count(); i += 2) { chars[i / 2] = QChar(mozilla[i], mozilla[i + 1]); if (mozilla.at(i) == 0x0A) { size = i / 2; name = &(chars[i / 2 + 1]); } } // Create a QString that take the address of the first QChar and a length if (name == 0L) { // We haven't found name (FIXME: Is it possible ?) QString normalHtml(&(chars[0]), chars.size()); return createNoteLink(normalHtml, parent); } else { QString normalHtml(&(chars[0]), size); QString normalTitle(name, chars.size() - size - 1); return createNoteLink(normalHtml, normalTitle, parent); } } if (source->hasFormat("text/html")) { QString html; QString subtype("html"); // If the text/html comes from Mozilla or GNOME it can be UTF-16 encoded: we need ExtendedTextDrag to check that ExtendedTextDrag::decode(source, html, subtype); return createNoteHtml(html, parent); } QString text; // If the text/plain comes from GEdit or GNOME it can be empty: we need ExtendedTextDrag to check other MIME types if (ExtendedTextDrag::decode(source, text)) return createNoteFromText(text, parent); /* Create a cross reference note */ if (source->hasFormat(BasketTreeListView::TREE_ITEM_MIME_STRING)) { QByteArray data = source->data(BasketTreeListView::TREE_ITEM_MIME_STRING); QDataStream stream(&data, QIODevice::ReadOnly); QString basketName, folderName, icon; while (!stream.atEnd()) stream >> basketName >> folderName >> icon; return createNoteCrossReference(QUrl("basket://" + folderName), basketName, icon, parent); } /* Unsuccessful drop */ note = createNoteUnknown(source, parent); QString message = i18n( "

    %1 doesn't support the data you've dropped.
    " "It however created a generic note, allowing you to drag or copy it to an application that understand it.

    " "

    If you want the support of these data, please contact developer.

    ", QGuiApplication::applicationDisplayName()); KMessageBox::information(parent->graphicsView()->viewport(), message, i18n("Unsupported MIME Type(s)"), "unsupportedDropInfo", KMessageBox::AllowLink); return note; } Note *NoteFactory::createNoteUnknown(const QMimeData *source, BasketScene *parent /*, const QString &annotations*/) { // Save the MimeSource in a file: create and open the file: QString fileName = createFileForNewNote(parent, "unknown"); QFile file(parent->fullPath() + fileName); if (!file.open(QIODevice::WriteOnly)) return 0L; QDataStream stream(&file); // Echo MIME types: QStringList formats = source->formats(); for (int i = 0; formats.size(); ++i) stream << QString(formats[i]); // Output the '\0'-terminated format name string // Echo end of MIME types list delimiter: stream << ""; // Echo the length (in bytes) and then the data, and then same for next MIME type: for (int i = 0; formats.size(); ++i) { QByteArray data = source->data(formats[i]); stream << (quint32)data.count(); stream.writeRawData(data.data(), data.count()); } file.close(); Note *note = new Note(parent); new UnknownContent(note, fileName); return note; } Note *NoteFactory::dropURLs(QList urls, BasketScene *parent, Qt::DropAction action, bool fromDrop) { KModifierKeyInfo keyinfo; int shouldAsk = 0; // shouldAsk==0: don't ask ; shouldAsk==1: ask for "file" ; shouldAsk>=2: ask for "files" bool shiftPressed = keyinfo.isKeyPressed(Qt::Key_Shift); bool ctrlPressed = keyinfo.isKeyPressed(Qt::Key_Control); bool modified = fromDrop && (shiftPressed || ctrlPressed); if (modified) // Then no menu + modified action ; // action is already set: no work to do else if (fromDrop) { // Compute if user should be asked or not for (QList::iterator it = urls.begin(); it != urls.end(); ++it) if ((*it).scheme() != "mailto") { // Do not ask when dropping mail address :-) shouldAsk++; if (shouldAsk == 1 /*2*/) // Sufficient break; } if (shouldAsk) { QMenu menu(parent->graphicsView()); QList actList; actList << new QAction(QIcon::fromTheme("go-jump"), i18n("&Move Here\tShift"), &menu) << new QAction(QIcon::fromTheme("edit-copy"), i18n("&Copy Here\tCtrl"), &menu) << new QAction(QIcon::fromTheme("insert-link"), i18n("&Link Here\tCtrl+Shift"), &menu); foreach (QAction *a, actList) menu.addAction(a); menu.addSeparator(); menu.addAction(QIcon::fromTheme("dialog-cancel"), i18n("C&ancel\tEscape")); int id = actList.indexOf(menu.exec(QCursor::pos())); switch (id) { case 0: action = Qt::MoveAction; break; case 1: action = Qt::CopyAction; break; case 2: action = Qt::LinkAction; break; default: return 0; } modified = true; } } else { // fromPaste ; } /* Policy of drops of URL: * Email: [Modifier keys: Useless] + - Link mail address * Remote URL: [Modifier keys: {Copy,Link}] + - Download as Image, Animation and Launcher + - Link other URLs * Local URL: [Modifier keys: {Copy,Move,Link}] * - Copy as Image, Animation and Launcher [Modifier keys: {Copy,Move,Link}] * - Link folder [Modifier keys: Useless] * - Make Launcher of executable [Modifier keys: {Copy_exec,Move_exec,Link_Launcher}] * - Ask for file (if use want to copy and it is a sound: make Sound) * Policy of pastes of URL: [NO modifier keys] * - Same as drops * - But copy when ask should be done * - Unless cut-selection is true: move files instead * Policy of file created in the basket dir: [NO modifier keys] * - View as Image, Animation, Sound, Launcher * - View as File */ Note *note; Note *firstNote = 0; Note *lastInserted = 0; for (QList::iterator it = urls.begin(); it != urls.end(); ++it) { if (((*it).scheme() == "mailto") || (action == Qt::LinkAction)) note = createNoteLinkOrLauncher(*it, parent); // else if (!(*it).isLocalFile()) { // if (action != Qt::LinkAction && (maybeImageOrAnimation(*it)/* || maybeSound(*it)*/)) // note = copyFileAndLoad(*it, parent); // else // note = createNoteLinkOrLauncher(*it, parent); // } else { if (action == Qt::CopyAction) note = copyFileAndLoad(*it, parent); else if (action == Qt::MoveAction) note = moveFileAndLoad(*it, parent); else note = createNoteLinkOrLauncher(*it, parent); } // If we got a new note, insert it in a linked list (we will return the first note of that list): if (note) { DEBUG_WIN << "Drop URL: " + (*it).toDisplayString(); if (!firstNote) firstNote = note; else { lastInserted->setNext(note); note->setPrev(lastInserted); } lastInserted = note; } } return firstNote; } void NoteFactory::consumeContent(QDataStream &stream, NoteType::Id type) { if (type == NoteType::Link) { QUrl url; QString title, icon; quint64 autoTitle64, autoIcon64; stream >> url >> title >> icon >> autoTitle64 >> autoIcon64; } else if (type == NoteType::CrossReference) { QUrl url; QString title, icon; stream >> url >> title >> icon; } else if (type == NoteType::Color) { QColor color; stream >> color; } } Note *NoteFactory::decodeContent(QDataStream &stream, NoteType::Id type, BasketScene *parent) { /* if (type == NoteType::Text) { QString text; stream >> text; return NoteFactory::createNoteText(text, parent); } else if (type == NoteType::Html) { QString html; stream >> html; return NoteFactory::createNoteHtml(html, parent); } else if (type == NoteType::Image) { QPixmap pixmap; stream >> pixmap; return NoteFactory::createNoteImage(pixmap, parent); } else */ if (type == NoteType::Link) { QUrl url; QString title, icon; quint64 autoTitle64, autoIcon64; bool autoTitle, autoIcon; stream >> url >> title >> icon >> autoTitle64 >> autoIcon64; autoTitle = (bool)autoTitle64; autoIcon = (bool)autoIcon64; Note *note = NoteFactory::createNoteLink(url, parent); ((LinkContent *)(note->content()))->setLink(url, title, icon, autoTitle, autoIcon); return note; } else if (type == NoteType::CrossReference) { QUrl url; QString title, icon; stream >> url >> title >> icon; Note *note = NoteFactory::createNoteCrossReference(url, parent); ((CrossReferenceContent *)(note->content()))->setCrossReference(url, title, icon); return note; } else if (type == NoteType::Color) { QColor color; stream >> color; return NoteFactory::createNoteColor(color, parent); } else return 0; // NoteFactory::loadFile() is sufficient } bool NoteFactory::maybeText(const QMimeType &mimeType) { return mimeType.inherits(MimeTypes::TEXT); } bool NoteFactory::maybeHtml(const QMimeType &mimeType) { return mimeType.inherits(MimeTypes::HTML); } bool NoteFactory::maybeImage(const QMimeType &mimeType) { return mimeType.name().startsWith(MimeTypes::IMAGE); } bool NoteFactory::maybeAnimation(const QMimeType &mimeType) { return mimeType.inherits(MimeTypes::ANIMATION) || mimeType.name() == MimeTypes::ANIMATION_MNG; } bool NoteFactory::maybeSound(const QMimeType &mimeType) { return mimeType.name().startsWith(MimeTypes::AUDIO); } bool NoteFactory::maybeLauncher(const QMimeType &mimeType) { return mimeType.inherits(MimeTypes::LAUNCHER); } ////////////// NEW: Note *NoteFactory::copyFileAndLoad(const QUrl &url, BasketScene *parent) { QString fileName = fileNameForNewNote(parent, url.fileName()); QString fullPath = parent->fullPathForFileName(fileName); if (Global::debugWindow) *Global::debugWindow << "copyFileAndLoad: " + url.toDisplayString() + " to " + fullPath; // QString annotations = i18n("Original file: %1", url.toDisplayString()); // parent->dontCareOfCreation(fullPath); KIO::CopyJob *copyJob = KIO::copy(url, QUrl::fromLocalFile(fullPath), KIO::Overwrite | KIO::Resume); parent->connect(copyJob, &KIO::CopyJob::copyingDone, parent, &BasketScene::slotCopyingDone2); NoteType::Id type = typeForURL(url, parent); // Use the type of the original file because the target doesn't exist yet return loadFile(fileName, type, parent); } Note *NoteFactory::moveFileAndLoad(const QUrl &url, BasketScene *parent) { // Globally the same as copyFileAndLoad() but move instead of copy (KIO::move()) QString fileName = fileNameForNewNote(parent, url.fileName()); QString fullPath = parent->fullPathForFileName(fileName); if (Global::debugWindow) *Global::debugWindow << "moveFileAndLoad: " + url.toDisplayString() + " to " + fullPath; // QString annotations = i18n("Original file: %1", url.toDisplayString()); // parent->dontCareOfCreation(fullPath); KIO::CopyJob *copyJob = KIO::move(url, QUrl::fromLocalFile(fullPath), KIO::Overwrite | KIO::Resume); parent->connect(copyJob, &KIO::CopyJob::copyingDone, parent, &BasketScene::slotCopyingDone2); NoteType::Id type = typeForURL(url, parent); // Use the type of the original file because the target doesn't exist yet return loadFile(fileName, type, parent); } Note *NoteFactory::loadFile(const QString &fileName, BasketScene *parent) { // The file MUST exists QFileInfo file(QUrl::fromLocalFile(parent->fullPathForFileName(fileName)).path()); if (!file.exists()) return 0L; NoteType::Id type = typeForURL(parent->fullPathForFileName(fileName), parent); Note *note = loadFile(fileName, type, parent); return note; } Note *NoteFactory::loadFile(const QString &fileName, NoteType::Id type, BasketScene *parent) { Note *note = new Note(parent); switch (type) { case NoteType::Text: new TextContent(note, fileName); break; case NoteType::Html: new HtmlContent(note, fileName); break; case NoteType::Image: new ImageContent(note, fileName); break; case NoteType::Animation: new AnimationContent(note, fileName); break; case NoteType::Sound: new SoundContent(note, fileName); break; case NoteType::File: new FileContent(note, fileName); break; case NoteType::Launcher: new LauncherContent(note, fileName); break; case NoteType::Unknown: new UnknownContent(note, fileName); break; default: case NoteType::Link: case NoteType::CrossReference: case NoteType::Color: return 0; } return note; } NoteType::Id NoteFactory::typeForURL(const QUrl &url, BasketScene * /*parent*/) { bool viewText = Settings::viewTextFileContent(); bool viewHTML = Settings::viewHtmlFileContent(); bool viewImage = Settings::viewImageFileContent(); bool viewSound = Settings::viewSoundFileContent(); QMimeDatabase db; QMimeType mimeType = db.mimeTypeForUrl(url); if (Global::debugWindow) { if (mimeType.isValid()) *Global::debugWindow << "typeForURL: " + url.toDisplayString() + " ; MIME type = " + mimeType.name(); else *Global::debugWindow << "typeForURL: mimeType is empty for " + url.toDisplayString(); } // Go from specific to more generic if (maybeLauncher(mimeType)) return NoteType::Launcher; else if (viewHTML && (maybeHtml(mimeType))) return NoteType::Html; else if (viewText && maybeText(mimeType)) return NoteType::Text; else if (viewImage && maybeAnimation(mimeType)) return NoteType::Animation; // See Note::movieStatus(int) else if (viewImage && maybeImage(mimeType)) return NoteType::Image; // for more explanations else if (viewSound && maybeSound(mimeType)) return NoteType::Sound; else return NoteType::File; } QString NoteFactory::fileNameForNewNote(BasketScene *parent, const QString &wantedName) { return Tools::fileNameForNewFile(wantedName, parent->fullPath()); } // Create a file to store a new note in Basket parent and with extension extension. // If wantedName is provided, the function will first try to use this file name, or derive it if it's impossible // (extension willn't be used for that case) QString NoteFactory::createFileForNewNote(BasketScene *parent, const QString &extension, const QString &wantedName) { QString fileName; QString fullName; if (wantedName.isEmpty()) { // TODO: fileNameForNewNote(parent, "note1."+extension); QDir dir; int nb = parent->count() + 1; QString time = QTime::currentTime().toString("hhmmss"); for (;; ++nb) { fileName = QString("note%1-%2.%3").arg(nb).arg(time).arg(extension); fullName = parent->fullPath() + fileName; dir = QDir(fullName); if (!dir.exists(fullName)) break; } } else { fileName = fileNameForNewNote(parent, wantedName); fullName = parent->fullPath() + fileName; } // Create the file // parent->dontCareOfCreation(fullName); QFile file(fullName); file.open(QIODevice::WriteOnly); file.close(); return fileName; } QUrl NoteFactory::filteredURL(const QUrl &url) { // KURIFilter::filteredURI() is slow if the URL contains only letters, digits and '-' or '+'. // So, we don't use that function is that case: bool isSlow = true; for (int i = 0; i < url.url().length(); ++i) { QChar c = url.url()[i]; if (!c.isLetterOrNumber() && c != '-' && c != '+') { isSlow = false; break; } } if (isSlow) return url; else { QStringList list; list << QLatin1String("kshorturifilter") << QLatin1String("kuriikwsfilter") << QLatin1String("kurisearchfilter") << QLatin1String("localdomainfilter") << QLatin1String("fixuphosturifilter"); return KUriFilter::self()->filteredUri(url, list); } } QString NoteFactory::titleForURL(const QUrl &url) { QString title = url.toDisplayString(); QString home = "file:" + QDir::homePath() + '/'; if (title.startsWith(QLatin1String("mailto:"))) return title.remove(0, 7); if (title.startsWith(home)) title = "~/" + title.remove(0, home.length()); if (title.startsWith(QLatin1String("file://"))) title = title.remove(0, 7); // 7 == QString("file://").length() - 1 else if (title.startsWith(QLatin1String("file:"))) title = title.remove(0, 5); // 5 == QString("file:").length() - 1 else if (title.startsWith(QLatin1String("http://www."))) title = title.remove(0, 11); // 11 == QString("http://www.").length() - 1 else if (title.startsWith(QLatin1String("https://www."))) title = title.remove(0, 12); // 12 == QString("https://www.").length() - 1 else if (title.startsWith(QLatin1String("http://"))) title = title.remove(0, 7); // 7 == QString("http://").length() - 1 else if (title.startsWith(QLatin1String("https://"))) title = title.remove(0, 8); // 8 == QString("https://").length() - 1 if (!url.isLocalFile()) { if (title.endsWith(QLatin1String("/index.html")) && title.length() > 11) title.truncate(title.length() - 11); // 11 == QString("/index.html").length() else if (title.endsWith(QLatin1String("/index.htm")) && title.length() > 10) title.truncate(title.length() - 10); // 10 == QString("/index.htm").length() else if (title.endsWith(QLatin1String("/index.xhtml")) && title.length() > 12) title.truncate(title.length() - 12); // 12 == QString("/index.xhtml").length() else if (title.endsWith(QLatin1String("/index.php")) && title.length() > 10) title.truncate(title.length() - 10); // 10 == QString("/index.php").length() else if (title.endsWith(QLatin1String("/index.asp")) && title.length() > 10) title.truncate(title.length() - 10); // 10 == QString("/index.asp").length() else if (title.endsWith(QLatin1String("/index.php3")) && title.length() > 11) title.truncate(title.length() - 11); // 11 == QString("/index.php3").length() else if (title.endsWith(QLatin1String("/index.php4")) && title.length() > 11) title.truncate(title.length() - 11); // 11 == QString("/index.php4").length() else if (title.endsWith(QLatin1String("/index.php5")) && title.length() > 11) title.truncate(title.length() - 11); // 11 == QString("/index.php5").length() } if (title.length() > 2 && title.endsWith('/')) // length > 2 because "/" and "~/" shouldn't be transformed to "" and "~" title.truncate(title.length() - 1); // eg. transform "www.kde.org/" to "www.kde.org" return title; } QString NoteFactory::iconForURL(const QUrl &url) { QString icon = ""; if (url.scheme() == "mailto") icon = "message"; // else // icon = KMimeType::iconNameForUrl(url.url()); return icon; } // TODO: Can I add "autoTitle" and "autoIcon" entries to .desktop files? or just store them in basket, as now... /* Try our better to find an icon suited to the command line * eg. "/usr/bin/kwrite-3.2 ~/myfile.txt /home/other/file.xml" * will give the "kwrite" icon! */ QString NoteFactory::iconForCommand(const QString &command) { QString icon; // 1. Use first word as icon (typically the program without argument) icon = command.split(' ').first(); // 2. If the command is a full path, take only the program file name icon = icon.mid(icon.lastIndexOf('/') + 1); // strip path if given [But it doesn't care of such // "myprogram /my/path/argument" -> return "argument". Would // must first strip first word and then strip path... Useful ?? // 3. Use characters before any '-' (e.g. use "gimp" icon if run command is "gimp-1.3") if (!isIconExist(icon)) icon = icon.split('-').first(); // 4. If the icon still not findable, use a generic icon if (!isIconExist(icon)) icon = "exec"; return icon; } bool NoteFactory::isIconExist(const QString &icon) { return !KIconLoader::global()->loadIcon(icon, KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true).isNull(); } Note *NoteFactory::createEmptyNote(NoteType::Id type, BasketScene *parent) { QPixmap *pixmap; switch (type) { case NoteType::Text: return NoteFactory::createNoteText("", parent, /*reallyPlainText=*/true); case NoteType::Html: return NoteFactory::createNoteHtml("", parent); case NoteType::Image: pixmap = new QPixmap(QSize(Settings::defImageX(), Settings::defImageY())); pixmap->fill(); pixmap->setMask(pixmap->createHeuristicMask()); return NoteFactory::createNoteImage(*pixmap, parent); case NoteType::Link: return NoteFactory::createNoteLink(QUrl(), parent); case NoteType::CrossReference: return NoteFactory::createNoteCrossReference(QUrl(), parent); case NoteType::Launcher: return NoteFactory::createNoteLauncher(QUrl(), parent); case NoteType::Color: return NoteFactory::createNoteColor(Qt::black, parent); default: case NoteType::Animation: case NoteType::Sound: case NoteType::File: case NoteType::Unknown: return 0; // Not possible! } } Note *NoteFactory::importKMenuLauncher(BasketScene *parent) { QPointer dialog = new KOpenWithDialog(parent->graphicsView()->viewport()); dialog->setSaveNewApplications(true); // To create temp file, needed by createNoteLauncher() dialog->exec(); if (dialog->service()) { // * locateLocal() return a local file even if it is a system wide one (local one doesn't exists) // * desktopEntryPath() returns the full path for system wide resources, but relative path if in home QString serviceFilePath = dialog->service()->entryPath(); if (!serviceFilePath.startsWith('/')) serviceFilePath = dialog->service()->locateLocal(); return createNoteLauncher(QUrl::fromUserInput(serviceFilePath), parent); } return 0; } Note *NoteFactory::importIcon(BasketScene *parent) { QString iconName = KIconDialog::getIcon(KIconLoader::Desktop, KIconLoader::Application, false, Settings::defIconSize()); if (!iconName.isEmpty()) { QPointer dialog = new IconSizeDialog(i18n("Import Icon as Image"), i18n("Choose the size of the icon to import as an image:"), iconName, Settings::defIconSize(), 0); dialog->exec(); if (dialog->iconSize() > 0) { Settings::setDefIconSize(dialog->iconSize()); Settings::saveConfig(); return createNoteImage(DesktopIcon(iconName, dialog->iconSize()), parent); // TODO: wantedName = iconName ! } } return 0; } Note *NoteFactory::importFileContent(BasketScene *parent) { QUrl url = QFileDialog::getOpenFileUrl(parent->graphicsView(), i18n("Load File Content into a Note"), QUrl(), ""); if (!url.isEmpty()) return copyFileAndLoad(url, parent); return 0; } diff --git a/src/notefactory.h b/src/notefactory.h index 249da01..51aa570 100644 --- a/src/notefactory.h +++ b/src/notefactory.h @@ -1,97 +1,84 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef NOTEFACTORY_H #define NOTEFACTORY_H #include "notecontent.h" //For NoteType::Id class QColor; class QPixmap; class QString; class QStringList; class QUrl; class QMimeType; class BasketScene; class Note; /** Factory class to create (new, drop, paste) or load BasketIem, and eventually save them (?) * @author Sébastien Laoût */ namespace NoteFactory { /** Functions to create a new note from a content item. * Content, if any, is saved to file but the note is not inserted in the basket, and the basket is not saved. * Return 0 if the note has not been successfully created. * In some cases, the returned note can be a group containing several notes or the first note of a chained list. * The method BasketScene::TODO() can insert several grouped or chained notes without problem. */ Note *createNoteText(const QString &text, BasketScene *parent, bool reallyPlainText = false); Note *createNoteHtml(const QString &html, BasketScene *parent); Note *createNoteLink(const QUrl &url, BasketScene *parent); Note *createNoteLink(const QUrl &url, const QString &title, BasketScene *parent); Note *createNoteCrossReference(const QUrl &url, BasketScene *parent); Note *createNoteCrossReference(const QUrl &url, const QString &title, BasketScene *parent); Note *createNoteCrossReference(const QUrl &url, const QString &title, const QString &icon, BasketScene *parent); Note *createNoteImage(const QPixmap &image, BasketScene *parent); Note *createNoteColor(const QColor &color, BasketScene *parent); Note *createNoteFromText(const QString &content, BasketScene *parent); // Find automatically the type from the text meaning // TODO: Return Note::List? Note *createNoteLauncher(const QUrl &url, BasketScene *parent); Note *createNoteLauncher(const QString &command, const QString &name, const QString &icon, BasketScene *parent); Note *createNoteUnknown(const QMimeData *source, BasketScene *parent); /** Functions to create derived notes from a content */ Note *createNoteLinkOrLauncher(const QUrl &url, BasketScene *parent); Note *copyFileAndLoad(const QUrl &url, BasketScene *parent); Note *moveFileAndLoad(const QUrl &url, BasketScene *parent); Note *loadFile(const QString &fileName, BasketScene *parent); /// << Determine the content of the file (the file SHOULD exists) and return a note of the good type. Note *loadFile(const QString &fileName, NoteType::Id type, BasketScene *parent); /// << Create a note of type @p type. The file is not obliged to exist. /** Functions to create a new note from a drop or paste event */ Note *dropNote(const QMimeData *source, BasketScene *parent, bool fromDrop = false, Qt::DropAction action = Qt::CopyAction, Note *noteSource = 0); bool movingNotesInTheSameBasket(const QMimeData *source, BasketScene *parent, Qt::DropAction action); Note *dropURLs(QList urls, BasketScene *parent, Qt::DropAction action, bool fromDrop); Note *decodeContent(QDataStream &stream, NoteType::Id type, BasketScene *parent); /// << Decode the @p stream to a note or return 0 if a general loadFile() is sufficient. void consumeContent(QDataStream &stream, NoteType::Id type); /// << Decode the @p stream to a note or return 0 if a general loadFile() is sufficient. /** Functions to create a note file but not load it in a note object */ QString createNoteLauncherFile(const QString &command, const QString &name, const QString &icon, BasketScene *parent); /** Other useful functions */ NoteType::Id typeForURL(const QUrl &url, BasketScene *parent); bool maybeText(const QMimeType &mimeType); bool maybeHtml(const QMimeType &mimeType); bool maybeImage(const QMimeType &mimeType); bool maybeAnimation(const QMimeType &mimeType); bool maybeSound(const QMimeType &mimeType); bool maybeLauncher(const QMimeType &mimeType); QString fileNameForNewNote(BasketScene *parent, const QString &wantedName); QString createFileForNewNote(BasketScene *parent, const QString &extension, const QString &wantedName = ""); QUrl filteredURL(const QUrl &url); QString titleForURL(const QUrl &url); QString iconForURL(const QUrl &url); QString iconForCommand(const QString &command); bool isIconExist(const QString &icon); QStringList textToURLList(const QString &text); // @Return { url1, title1, url2, title2, url3, title3... } /** Insert GUI menu */ Note *createEmptyNote(NoteType::Id type, BasketScene *parent); // Insert empty if of type Note::Type Note *importKMenuLauncher(BasketScene *parent); Note *importIcon(BasketScene *parent); Note *importFileContent(BasketScene *parent); } #endif // NOTEFACTORY_H diff --git a/src/noteselection.cpp b/src/noteselection.cpp index 749fb9d..4b8cfc9 100644 --- a/src/noteselection.cpp +++ b/src/noteselection.cpp @@ -1,130 +1,117 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "noteselection.h" #include #include "note.h" #include "notecontent.h" /** Class NoteSelection: */ NoteSelection *NoteSelection::nextStacked() { // First, search in the children: if (firstChild) { if (firstChild->note && firstChild->note->content()) return firstChild; else return firstChild->nextStacked(); } // Then, in the next: if (next) { if (next->note && next->note->content()) return next; else return next->nextStacked(); } // And finally, in the parent: NoteSelection *node = parent; while (node) { if (node->next) { if (node->next->note && node->next->note->content()) return node->next; else return node->next->nextStacked(); } else node = node->parent; } // Not found: return 0; } NoteSelection *NoteSelection::firstStacked() { if (!this) return 0; if (note && note->content()) return this; else return nextStacked(); } void NoteSelection::append(NoteSelection *node) { if (!this || !node) return; if (firstChild) { NoteSelection *last = firstChild; while (last->next) last = last->next; last->next = node; } else firstChild = node; while (node) { node->parent = this; node = node->next; } } int NoteSelection::count() { if (!this) return 0; int count = 0; for (NoteSelection *node = this; node; node = node->next) if (node->note && node->note->content()) ++count; else count += node->firstChild->count(); return count; } QList NoteSelection::parentGroups() { QList groups; // For each note: for (NoteSelection *node = firstStacked(); node; node = node->nextStacked()) // For each parent groups of the note: for (Note *note = node->note->parentNote(); note; note = note->parentNote()) // Add it (if it was not already in the list): if (!note->isColumn() && !groups.contains(note)) groups.append(note); return groups; } void debugSel(NoteSelection *sel, int n = 0) { for (NoteSelection *node = sel; node; node = node->next) { for (int i = 0; i < n; i++) qDebug() << "-"; qDebug() << (node->firstChild ? "Group" : node->note->content()->toText("")); if (node->firstChild) debugSel(node->firstChild, n + 1); } } diff --git a/src/noteselection.h b/src/noteselection.h index 1323596..2142941 100644 --- a/src/noteselection.h +++ b/src/noteselection.h @@ -1,65 +1,52 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef NOTESELECTION_H #define NOTESELECTION_H #include #include class Note; /** This represent a hierarchy of the selected classes. * If this is null, then there is no selected note. */ class NoteSelection { public: NoteSelection() : note(0) , parent(0) , firstChild(0) , next(0) , fullPath() { } NoteSelection(Note *n) : note(n) , parent(0) , firstChild(0) , next(0) , fullPath() { } Note *note; NoteSelection *parent; NoteSelection *firstChild; NoteSelection *next; QString fullPath; // Needeed for 'Cut' code to store temporary path of the cut note. NoteSelection *firstStacked(); NoteSelection *nextStacked(); void append(NoteSelection *node); int count(); QList parentGroups(); }; #endif // NOTESELECTION_H diff --git a/src/password.cpp b/src/password.cpp index 537447c..f5883dc 100644 --- a/src/password.cpp +++ b/src/password.cpp @@ -1,141 +1,127 @@ -/*************************************************************************** - * Copyright (C) 2006 by Petri Damsten * - * damu@iki.fi * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2006 Petri Damsten + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "password.h" #ifdef HAVE_LIBGPGME #include #include #include #include #include #include #include #include #include "basketscene.h" #include "kgpgme.h" PasswordDlg::PasswordDlg(QWidget *parent) : QDialog(parent) , w(0) { // QDialog options setWindowTitle(i18n("Password Protection")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); okButton->setDefault(true); setModal(true); QHBoxLayout *toplayout = new QHBoxLayout(mainWidget); w = new Password; toplayout->addWidget(w, 1); } PasswordDlg::~PasswordDlg() { delete w; } void PasswordDlg::accept() { int n = type(); if (n == BasketScene::PrivateKeyEncryption && key().isEmpty()) KMessageBox::error(w, i18n("No private key selected.")); else QDialog::accept(); } QString PasswordDlg::key() const { QString s = w->keyCombo->currentText(); if (s.length() < 16) return ""; int n = s.lastIndexOf(' '); if (n < 0) return ""; return s.mid(n + 1); } int PasswordDlg::type() const { if (w->noPasswordRadioButton->isChecked()) return BasketScene::NoEncryption; else if (w->passwordRadioButton->isChecked()) return BasketScene::PasswordEncryption; else if (w->publicPrivateRadioButton->isChecked()) return BasketScene::PrivateKeyEncryption; return -1; } void PasswordDlg::setKey(const QString &key) { for (int i = 0; i < w->keyCombo->count(); ++i) { if (w->keyCombo->itemText(i).contains(key)) { w->keyCombo->setCurrentIndex(i); return; } } } void PasswordDlg::setType(int type) { if (type == BasketScene::NoEncryption) w->noPasswordRadioButton->setChecked(true); else if (type == BasketScene::PasswordEncryption) w->passwordRadioButton->setChecked(true); else if (type == BasketScene::PrivateKeyEncryption) w->publicPrivateRadioButton->setChecked(true); } Password::Password(QWidget *parent) : QWidget(parent) { // Setup from the UI file setupUi(this); KGpgMe gpg; KGpgKeyList list = gpg.keys(true); for (KGpgKeyList::iterator it = list.begin(); it != list.end(); ++it) { QString name = gpg.checkForUtf8((*it).name); keyCombo->addItem(QString("%1 <%2> %3").arg(name).arg((*it).email).arg((*it).id)); } publicPrivateRadioButton->setEnabled(keyCombo->count() > 0); keyCombo->setEnabled(keyCombo->count() > 0); } Password::~Password() { } #endif diff --git a/src/password.h b/src/password.h index d5952fc..079645e 100644 --- a/src/password.h +++ b/src/password.h @@ -1,63 +1,49 @@ -/*************************************************************************** - * Copyright (C) 2006 by Petri Damsten * - * damu@iki.fi * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2006 Petri Damsten + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef PASSWORD_H #define PASSWORD_H #include #ifdef HAVE_LIBGPGME #include "ui_passwordlayout.h" #include /** @author Petri Damsten */ class Password : public QWidget, public Ui::PasswordLayout { Q_OBJECT public: explicit Password(QWidget *parent = nullptr); ~Password(); }; class PasswordDlg : public QDialog { Q_OBJECT public: PasswordDlg(QWidget *parent = 0); ~PasswordDlg(); QString key() const; int type() const; void setKey(const QString &key); void setType(int type); /** Reimplemented from {K,Q}Dialog */ void accept(); private: Password *w; }; #endif // HAVE_LIBGPGME #endif // PASSWORD_H diff --git a/src/regiongrabber.cpp b/src/regiongrabber.cpp index fdffdef..6f151ae 100644 --- a/src/regiongrabber.cpp +++ b/src/regiongrabber.cpp @@ -1,334 +1,322 @@ -/* - * Copyright (C) 2007 Luca Gugelmann +/** + * SPDX-FileCopyrightText: (C) 2007 Luca Gugelmann * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU Library General Public License version 2 as - * published by the Free Software Foundation - * - * This 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 Library 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. + * SPDX-License-Identifier: LGPL-2.0-only */ #include "regiongrabber.h" #include #include #include #include #include #include #include #include RegionGrabber::RegionGrabber() : QWidget(0) , selection() , mouseDown(false) , newSelection(false) , handleSize(10) , mouseOverHandle(0) , idleTimer() , showHelp(true) , grabbing(false) , TLHandle(0, 0, handleSize, handleSize) , TRHandle(0, 0, handleSize, handleSize) , BLHandle(0, 0, handleSize, handleSize) , BRHandle(0, 0, handleSize, handleSize) , LHandle(0, 0, handleSize, handleSize) , THandle(0, 0, handleSize, handleSize) , RHandle(0, 0, handleSize, handleSize) , BHandle(0, 0, handleSize, handleSize) { handles << &TLHandle << &TRHandle << &BLHandle << &BRHandle << &LHandle << &THandle << &RHandle << &BHandle; setMouseTracking(true); setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint); int timeout = KWindowSystem::compositingActive() ? 200 : 50; QTimer::singleShot(timeout, this, SLOT(init())); connect(&idleTimer, SIGNAL(timeout()), this, SLOT(displayHelp())); idleTimer.start(3000); } RegionGrabber::~RegionGrabber() { } void RegionGrabber::init() { pixmap = QPixmap::grabWindow(QApplication::desktop()->winId()); showFullScreen(); // krazy:exclude=qmethods -- Necessary for proper screenshot capture. resize(pixmap.size()); move(0, 0); setCursor(Qt::CrossCursor); grabKeyboard(); } void RegionGrabber::displayHelp() { showHelp = true; update(); } void RegionGrabber::paintEvent(QPaintEvent *e) { Q_UNUSED(e); if (grabbing) // grabWindow() should just get the background return; QPainter painter(this); QPalette pal(QToolTip::palette()); QFont font = QToolTip::font(); QColor handleColor = pal.color(QPalette::Active, QPalette::Highlight); handleColor.setAlpha(160); QColor overlayColor(0, 0, 0, 160); QColor textColor = pal.color(QPalette::Active, QPalette::Text); QColor textBackgroundColor = pal.color(QPalette::Active, QPalette::Base); painter.drawPixmap(0, 0, pixmap); painter.setFont(font); QRect r = selection.normalized().adjusted(0, 0, -1, -1); if (!selection.isNull()) { QRegion grey(rect()); grey = grey.subtracted(r); painter.setPen(handleColor); painter.setBrush(overlayColor); painter.setClipRegion(grey); painter.drawRect(-1, -1, rect().width() + 1, rect().height() + 1); painter.setClipRect(rect()); painter.setBrush(Qt::NoBrush); painter.drawRect(r); } if (showHelp) { painter.setPen(textColor); painter.setBrush(textBackgroundColor); QString helpText = i18n("Select a region using the mouse. To take the snapshot, press the Enter key. Press Esc to quit."); QRect textRect = painter.boundingRect(rect().adjusted(2, 2, -2, -2), Qt::TextWordWrap, helpText); textRect.adjust(-2, -2, 4, 2); painter.drawRect(textRect); textRect.moveTopLeft(QPoint(3, 3)); painter.drawText(textRect, helpText); } if (selection.isNull()) { return; } // The grabbed region is everything which is covered by the drawn // rectangles (border included). This means that there is no 0px // selection, since a 0px wide rectangle will always be drawn as a line. QString txt = QString("%1x%2").arg(selection.width() == 0 ? 2 : selection.width()).arg(selection.height() == 0 ? 2 : selection.height()); QRect textRect = painter.boundingRect(rect(), Qt::AlignLeft, txt); QRect boundingRect = textRect.adjusted(-4, 0, 0, 0); if (textRect.width() < r.width() - 2 * handleSize && textRect.height() < r.height() - 2 * handleSize && (r.width() > 100 && r.height() > 100)) { // center, unsuitable for small selections boundingRect.moveCenter(r.center()); textRect.moveCenter(r.center()); } else if (r.y() - 3 > textRect.height() && r.x() + textRect.width() < rect().right()) { // on top, left aligned boundingRect.moveBottomLeft(QPoint(r.x(), r.y() - 3)); textRect.moveBottomLeft(QPoint(r.x() + 2, r.y() - 3)); } else if (r.x() - 3 > textRect.width()) { // left, top aligned boundingRect.moveTopRight(QPoint(r.x() - 3, r.y())); textRect.moveTopRight(QPoint(r.x() - 5, r.y())); } else if (r.bottom() + 3 + textRect.height() < rect().bottom() && r.right() > textRect.width()) { // at bottom, right aligned boundingRect.moveTopRight(QPoint(r.right(), r.bottom() + 3)); textRect.moveTopRight(QPoint(r.right() - 2, r.bottom() + 3)); } else if (r.right() + textRect.width() + 3 < rect().width()) { // right, bottom aligned boundingRect.moveBottomLeft(QPoint(r.right() + 3, r.bottom())); textRect.moveBottomLeft(QPoint(r.right() + 5, r.bottom())); } // if the above didn't catch it, you are running on a very tiny screen... painter.setPen(textColor); painter.setBrush(textBackgroundColor); painter.drawRect(boundingRect); painter.drawText(textRect, txt); if ((r.height() > handleSize * 2 && r.width() > handleSize * 2) || !mouseDown) { updateHandles(); painter.setPen(handleColor); handleColor.setAlpha(60); painter.setBrush(handleColor); painter.drawRects(handleMask().rects()); } } void RegionGrabber::resizeEvent(QResizeEvent *e) { Q_UNUSED(e); if (selection.isNull()) return; QRect r = selection; r.setTopLeft(limitPointToRect(r.topLeft(), rect())); r.setBottomRight(limitPointToRect(r.bottomRight(), rect())); if (r.width() <= 1 || r.height() <= 1) // this just results in ugly drawing... r = QRect(); selection = r; } void RegionGrabber::mousePressEvent(QMouseEvent *e) { showHelp = false; idleTimer.stop(); if (e->button() == Qt::LeftButton) { mouseDown = true; dragStartPoint = e->pos(); selectionBeforeDrag = selection; if (!selection.contains(e->pos())) { newSelection = true; selection = QRect(); showHelp = true; } else { setCursor(Qt::ClosedHandCursor); } } else if (e->button() == Qt::RightButton) { newSelection = false; selection = QRect(); setCursor(Qt::CrossCursor); } update(); } void RegionGrabber::mouseMoveEvent(QMouseEvent *e) { if (mouseDown) { if (newSelection) { QPoint p = e->pos(); QRect r = rect(); selection = QRect(dragStartPoint, limitPointToRect(p, r)).normalized(); } else if (mouseOverHandle == 0) { // moving the whole selection QRect r = rect().normalized(), s = selectionBeforeDrag.normalized(); QPoint p = s.topLeft() + e->pos() - dragStartPoint; r.setBottomRight(r.bottomRight() - QPoint(s.width(), s.height())); if (!r.isNull() && r.isValid()) selection.moveTo(limitPointToRect(p, r)); } else { // dragging a handle QRect r = selectionBeforeDrag; QPoint offset = e->pos() - dragStartPoint; if (mouseOverHandle == &TLHandle || mouseOverHandle == &THandle || mouseOverHandle == &TRHandle) { // dragging one of the top handles r.setTop(r.top() + offset.y()); } if (mouseOverHandle == &TLHandle || mouseOverHandle == &LHandle || mouseOverHandle == &BLHandle) { // dragging one of the left handles r.setLeft(r.left() + offset.x()); } if (mouseOverHandle == &BLHandle || mouseOverHandle == &BHandle || mouseOverHandle == &BRHandle) { // dragging one of the bottom handles r.setBottom(r.bottom() + offset.y()); } if (mouseOverHandle == &TRHandle || mouseOverHandle == &RHandle || mouseOverHandle == &BRHandle) { // dragging one of the right handles r.setRight(r.right() + offset.x()); } r = r.normalized(); r.setTopLeft(limitPointToRect(r.topLeft(), rect())); r.setBottomRight(limitPointToRect(r.bottomRight(), rect())); selection = r; } update(); } else { if (selection.isNull()) return; bool found = false; foreach (QRect *r, handles) { if (r->contains(e->pos())) { mouseOverHandle = r; found = true; break; } } if (!found) { mouseOverHandle = 0; if (selection.contains(e->pos())) setCursor(Qt::OpenHandCursor); else setCursor(Qt::CrossCursor); } else { if (mouseOverHandle == &TLHandle || mouseOverHandle == &BRHandle) setCursor(Qt::SizeFDiagCursor); if (mouseOverHandle == &TRHandle || mouseOverHandle == &BLHandle) setCursor(Qt::SizeBDiagCursor); if (mouseOverHandle == &LHandle || mouseOverHandle == &RHandle) setCursor(Qt::SizeHorCursor); if (mouseOverHandle == &THandle || mouseOverHandle == &BHandle) setCursor(Qt::SizeVerCursor); } } } void RegionGrabber::mouseReleaseEvent(QMouseEvent *e) { mouseDown = false; newSelection = false; idleTimer.start(); if (mouseOverHandle == 0 && selection.contains(e->pos())) setCursor(Qt::OpenHandCursor); update(); } void RegionGrabber::mouseDoubleClickEvent(QMouseEvent *) { grabRect(); } void RegionGrabber::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { emit regionGrabbed(QPixmap()); } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { grabRect(); } else { e->ignore(); } } void RegionGrabber::grabRect() { QRect r = selection.normalized(); if (!r.isNull() && r.isValid()) { grabbing = true; emit regionGrabbed(pixmap.copy(r)); } } void RegionGrabber::updateHandles() { QRect r = selection.normalized().adjusted(0, 0, -1, -1); int s2 = handleSize / 2; TLHandle.moveTopLeft(r.topLeft()); TRHandle.moveTopRight(r.topRight()); BLHandle.moveBottomLeft(r.bottomLeft()); BRHandle.moveBottomRight(r.bottomRight()); LHandle.moveTopLeft(QPoint(r.x(), r.y() + r.height() / 2 - s2)); THandle.moveTopLeft(QPoint(r.x() + r.width() / 2 - s2, r.y())); RHandle.moveTopRight(QPoint(r.right(), r.y() + r.height() / 2 - s2)); BHandle.moveBottomLeft(QPoint(r.x() + r.width() / 2 - s2, r.bottom())); } QRegion RegionGrabber::handleMask() const { // note: not normalized QRects are bad here, since they will not be drawn QRegion mask; foreach (QRect *rect, handles) mask += QRegion(*rect); return mask; } QPoint RegionGrabber::limitPointToRect(const QPoint &p, const QRect &r) const { QPoint q; q.setX(p.x() < r.x() ? r.x() : p.x() < r.right() ? p.x() : r.right()); q.setY(p.y() < r.y() ? r.y() : p.y() < r.bottom() ? p.y() : r.bottom()); return q; } diff --git a/src/regiongrabber.h b/src/regiongrabber.h index 25b138b..5f51331 100644 --- a/src/regiongrabber.h +++ b/src/regiongrabber.h @@ -1,82 +1,70 @@ -/* - * Copyright (C) 2007 Luca Gugelmann +/** + * SPDX-FileCopyrightText: (C) 2007 Luca Gugelmann * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU Library General Public License version 2 as - * published by the Free Software Foundation - * - * This 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 Library 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. + * SPDX-License-Identifier: LGPL-2.0-only */ #ifndef REGIONGRABBER_H #define REGIONGRABBER_H #include #include #include class QPoint; class QRect; class QRegion; class QPaintEvent; class QResizeEvent; class QMouseEvent; class RegionGrabber : public QWidget { Q_OBJECT public: RegionGrabber(); ~RegionGrabber() override; protected slots: void init(); void displayHelp(); signals: void regionGrabbed(const QPixmap &); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseDoubleClickEvent(QMouseEvent *) override; void keyPressEvent(QKeyEvent *e) override; void updateHandles(); QRegion handleMask() const; QPoint limitPointToRect(const QPoint &p, const QRect &r) const; void grabRect(); QRect selection; bool mouseDown; bool newSelection; const int handleSize; QRect *mouseOverHandle; QPoint dragStartPoint; QRect selectionBeforeDrag; QTimer idleTimer; bool showHelp; bool grabbing; // naming convention for handles // T top, B bottom, R Right, L left // 2 letters: a corner // 1 letter: the handle on the middle of the corresponding side QRect TLHandle, TRHandle, BLHandle, BRHandle; QRect LHandle, THandle, RHandle, BHandle; QVector handles; QPixmap pixmap; }; #endif diff --git a/src/settings.cpp b/src/settings.cpp index 5ef829d..d0d56d6 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1,1071 +1,1058 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "aboutdata.h" #include "basketscene.h" #include "kgpgme.h" #include "linklabel.h" #include "variouswidgets.h" /** Settings */ // General: // TODO: Use this grouping everywhere! bool Settings::s_useSystray = true; bool Settings::s_usePassivePopup = true; bool Settings::s_playAnimations = true; bool Settings::s_showNotesToolTip = true; // TODO: RENAME: useBasketTooltips bool Settings::s_confirmNoteDeletion = true; bool Settings::s_bigNotes = false; bool Settings::s_autoBullet = true; bool Settings::s_pasteAsPlainText = false; bool Settings::s_exportTextTags = true; bool Settings::s_useGnuPGAgent = false; bool Settings::s_treeOnLeft = true; bool Settings::s_filterOnTop = false; int Settings::s_defImageX = 300; int Settings::s_defImageY = 200; bool Settings::s_enableReLockTimeout = true; int Settings::s_reLockTimeoutMinutes = 0; int Settings::s_newNotesPlace = 1; int Settings::s_viewTextFileContent = false; int Settings::s_viewHtmlFileContent = false; int Settings::s_viewImageFileContent = false; int Settings::s_viewSoundFileContent = false; // Applications: bool Settings::s_htmlUseProg = false; // TODO: RENAME: s_*App (with KService!) bool Settings::s_imageUseProg = true; bool Settings::s_animationUseProg = true; bool Settings::s_soundUseProg = false; QString Settings::s_htmlProg = "quanta"; QString Settings::s_imageProg = "kolourpaint"; QString Settings::s_animationProg = "gimp"; QString Settings::s_soundProg = ""; // Addictive Features: bool Settings::s_groupOnInsertionLine = false; int Settings::s_middleAction = 0; bool Settings::s_showIconInSystray = false; // TODO: RENAME: basketIconInSystray bool Settings::s_hideOnMouseOut = false; int Settings::s_timeToHideOnMouseOut = 0; bool Settings::s_showOnMouseIn = false; int Settings::s_timeToShowOnMouseIn = 1; // Rememberings: int Settings::s_defIconSize = 32; // TODO: RENAME: importIconSize bool Settings::s_blinkedFilter = false; bool Settings::s_startDocked = false; int Settings::s_basketTreeWidth = -1; bool Settings::s_welcomeBasketsAdded = false; QString Settings::s_dataFolder = ""; QDate Settings::s_lastBackup = QDate(); QPoint Settings::s_mainWindowPosition = QPoint(); QSize Settings::s_mainWindowSize = QSize(); bool Settings::s_showEmptyBasketInfo = true; bool Settings::s_spellCheckTextNotes = true; // Version Sync bool Settings::s_versionSyncEnabled = false; void Settings::loadConfig() { LinkLook defaultSoundLook; LinkLook defaultFileLook; LinkLook defaultLocalLinkLook; LinkLook defaultNetworkLinkLook; LinkLook defaultLauncherLook; /* italic bold underlining color hoverColor iconSize preview */ LinkLook defaultCrossReferenceLook; defaultSoundLook.setLook(false, false, LinkLook::Never, QColor(), QColor(), 32, LinkLook::None); defaultFileLook.setLook(false, false, LinkLook::Never, QColor(), QColor(), 32, LinkLook::TwiceIconSize); defaultLocalLinkLook.setLook(true, false, LinkLook::OnMouseHover, QColor(), QColor(), 22, LinkLook::TwiceIconSize); defaultNetworkLinkLook.setLook(false, false, LinkLook::OnMouseOutside, QColor(), QColor(), 16, LinkLook::None); defaultLauncherLook.setLook(false, true, LinkLook::Never, QColor(), QColor(), 48, LinkLook::None); defaultCrossReferenceLook.setLook(false, false, LinkLook::OnMouseHover, QColor(), QColor(), 16, LinkLook::None); loadLinkLook(LinkLook::soundLook, "Sound Look", defaultSoundLook); loadLinkLook(LinkLook::fileLook, "File Look", defaultFileLook); loadLinkLook(LinkLook::localLinkLook, "Local Link Look", defaultLocalLinkLook); loadLinkLook(LinkLook::networkLinkLook, "Network Link Look", defaultNetworkLinkLook); loadLinkLook(LinkLook::launcherLook, "Launcher Look", defaultLauncherLook); loadLinkLook(LinkLook::crossReferenceLook, "Cross Reference Look", defaultCrossReferenceLook); KConfigGroup config = Global::config()->group("Main window"); // TODO: Split with a "System tray icon" group ! setTreeOnLeft(config.readEntry("treeOnLeft", true)); setFilterOnTop(config.readEntry("filterOnTop", false)); setPlayAnimations(config.readEntry("playAnimations", true)); setShowNotesToolTip(config.readEntry("showNotesToolTip", true)); setBigNotes(config.readEntry("bigNotes", false)); setConfirmNoteDeletion(config.readEntry("confirmNoteDeletion", true)); setPasteAsPlainText(config.readEntry("pasteAsPlainText", false)); setAutoBullet(config.readEntry("autoBullet", true)); setExportTextTags(config.readEntry("exportTextTags", true)); setUseGnuPGAgent(config.readEntry("useGnuPGAgent", false)); setBlinkedFilter(config.readEntry("blinkedFilter", false)); setEnableReLockTimeout(config.readEntry("enableReLockTimeout", true)); setReLockTimeoutMinutes(config.readEntry("reLockTimeoutMinutes", 0)); setUseSystray(config.readEntry("useSystray", true)); setShowIconInSystray(config.readEntry("showIconInSystray", false)); setStartDocked(config.readEntry("startDocked", false)); setMiddleAction(config.readEntry("middleAction", 0)); setGroupOnInsertionLine(config.readEntry("groupOnInsertionLine", false)); setSpellCheckTextNotes(config.readEntry("spellCheckTextNotes", true)); setHideOnMouseOut(config.readEntry("hideOnMouseOut", false)); setTimeToHideOnMouseOut(config.readEntry("timeToHideOnMouseOut", 0)); setShowOnMouseIn(config.readEntry("showOnMouseIn", false)); setTimeToShowOnMouseIn(config.readEntry("timeToShowOnMouseIn", 1)); setBasketTreeWidth(config.readEntry("basketTreeWidth", -1)); setUsePassivePopup(config.readEntry("usePassivePopup", true)); setWelcomeBasketsAdded(config.readEntry("welcomeBasketsAdded", false)); setDataFolder(config.readEntry("dataFolder", "")); setLastBackup(config.readEntry("lastBackup", QDate())); setMainWindowPosition(config.readEntry("position", QPoint())); setMainWindowSize(config.readEntry("size", QSize())); config = Global::config()->group("Notification Messages"); setShowEmptyBasketInfo(config.readEntry("emptyBasketInfo", true)); config = Global::config()->group("Programs"); setIsHtmlUseProg(config.readEntry("htmlUseProg", false)); setIsImageUseProg(config.readEntry("imageUseProg", true)); setIsAnimationUseProg(config.readEntry("animationUseProg", true)); setIsSoundUseProg(config.readEntry("soundUseProg", false)); setHtmlProg(config.readEntry("htmlProg", "quanta")); setImageProg(config.readEntry("imageProg", "kolourpaint")); setAnimationProg(config.readEntry("animationProg", "gimp")); setSoundProg(config.readEntry("soundProg", "")); config = Global::config()->group("Note Addition"); setNewNotesPlace(config.readEntry("newNotesPlace", 1)); setViewTextFileContent(config.readEntry("viewTextFileContent", false)); setViewHtmlFileContent(config.readEntry("viewHtmlFileContent", false)); setViewImageFileContent(config.readEntry("viewImageFileContent", true)); setViewSoundFileContent(config.readEntry("viewSoundFileContent", true)); config = Global::config()->group("Insert Note Default Values"); setDefImageX(config.readEntry("defImageX", 300)); setDefImageY(config.readEntry("defImageY", 200)); setDefIconSize(config.readEntry("defIconSize", 32)); config = Global::config()->group("MainWindow Toolbar mainToolBar"); // The first time we start, we define "Text Alongside Icons" for the main toolbar. // After that, the user is free to hide the text from the icons or customize as he/she want. // But it is a good default (Fitt's Laws, better looking, less "empty"-feeling), especially for this application. // if (!config->readEntry("alreadySetIconTextRight", false)) { // config->writeEntry( "IconText", "IconTextRight" ); // config->writeEntry( "alreadySetIconTextRight", true ); // } if (!config.readEntry("alreadySetToolbarSettings", false)) { config.writeEntry("IconText", "IconOnly"); // In 0.6.0 Alpha versions, it was made "IconTextRight". We're back to IconOnly config.writeEntry("Index", "0"); // Make sure the main toolbar is the first... config = Global::config()->group("MainWindow Toolbar richTextEditToolBar"); config.writeEntry("Position", "Top"); // In 0.6.0 Alpha versions, it was made "Bottom" config.writeEntry("Index", "1"); // ... and the rich text toolbar is on the right of the main toolbar config = Global::config()->group("MainWindow Toolbar mainToolBar"); config.writeEntry("alreadySetToolbarSettings", true); } config = Global::config()->group("Version Sync"); setVersionSyncEnabled(config.readEntry("enabled", false)); } void Settings::saveConfig() { saveLinkLook(LinkLook::soundLook, "Sound Look"); saveLinkLook(LinkLook::fileLook, "File Look"); saveLinkLook(LinkLook::localLinkLook, "Local Link Look"); saveLinkLook(LinkLook::networkLinkLook, "Network Link Look"); saveLinkLook(LinkLook::launcherLook, "Launcher Look"); saveLinkLook(LinkLook::crossReferenceLook, "Cross Reference Look"); KConfigGroup config = Global::config()->group("Main window"); config.writeEntry("treeOnLeft", treeOnLeft()); config.writeEntry("filterOnTop", filterOnTop()); config.writeEntry("playAnimations", playAnimations()); config.writeEntry("showNotesToolTip", showNotesToolTip()); config.writeEntry("confirmNoteDeletion", confirmNoteDeletion()); config.writeEntry("pasteAsPlainText", pasteAsPlainText()); config.writeEntry("bigNotes", bigNotes()); config.writeEntry("autoBullet", autoBullet()); config.writeEntry("exportTextTags", exportTextTags()); #ifdef HAVE_LIBGPGME if (KGpgMe::isGnuPGAgentAvailable()) config.writeEntry("useGnuPGAgent", useGnuPGAgent()); #endif config.writeEntry("blinkedFilter", blinkedFilter()); config.writeEntry("enableReLockTimeout", enableReLockTimeout()); config.writeEntry("reLockTimeoutMinutes", reLockTimeoutMinutes()); config.writeEntry("useSystray", useSystray()); config.writeEntry("showIconInSystray", showIconInSystray()); config.writeEntry("startDocked", startDocked()); config.writeEntry("middleAction", middleAction()); config.writeEntry("groupOnInsertionLine", groupOnInsertionLine()); config.writeEntry("spellCheckTextNotes", spellCheckTextNotes()); config.writeEntry("hideOnMouseOut", hideOnMouseOut()); config.writeEntry("timeToHideOnMouseOut", timeToHideOnMouseOut()); config.writeEntry("showOnMouseIn", showOnMouseIn()); config.writeEntry("timeToShowOnMouseIn", timeToShowOnMouseIn()); config.writeEntry("basketTreeWidth", basketTreeWidth()); config.writeEntry("usePassivePopup", usePassivePopup()); config.writeEntry("welcomeBasketsAdded", welcomeBasketsAdded()); config.writePathEntry("dataFolder", dataFolder()); config.writeEntry("lastBackup", QDate(lastBackup())); config.writeEntry("position", mainWindowPosition()); config.writeEntry("size", mainWindowSize()); config = Global::config()->group("Notification Messages"); config.writeEntry("emptyBasketInfo", showEmptyBasketInfo()); config = Global::config()->group("Programs"); config.writeEntry("htmlUseProg", isHtmlUseProg()); config.writeEntry("imageUseProg", isImageUseProg()); config.writeEntry("animationUseProg", isAnimationUseProg()); config.writeEntry("soundUseProg", isSoundUseProg()); config.writeEntry("htmlProg", htmlProg()); config.writeEntry("imageProg", imageProg()); config.writeEntry("animationProg", animationProg()); config.writeEntry("soundProg", soundProg()); config = Global::config()->group("Note Addition"); config.writeEntry("newNotesPlace", newNotesPlace()); config.writeEntry("viewTextFileContent", viewTextFileContent()); config.writeEntry("viewHtmlFileContent", viewHtmlFileContent()); config.writeEntry("viewImageFileContent", viewImageFileContent()); config.writeEntry("viewSoundFileContent", viewSoundFileContent()); config = Global::config()->group("Insert Note Default Values"); config.writeEntry("defImageX", defImageX()); config.writeEntry("defImageY", defImageY()); config.writeEntry("defIconSize", defIconSize()); config = Global::config()->group("Version Sync"); config.writeEntry("enabled", versionSyncEnabled()); config.sync(); } void Settings::loadLinkLook(LinkLook *look, const QString &name, const LinkLook &defaultLook) { KConfigGroup config = Global::config()->group(name); QString underliningStrings[] = {"Always", "Never", "OnMouseHover", "OnMouseOutside"}; QString defaultUnderliningString = underliningStrings[defaultLook.underlining()]; QString previewStrings[] = {"None", "IconSize", "TwiceIconSize", "ThreeIconSize"}; QString defaultPreviewString = previewStrings[defaultLook.preview()]; bool italic = config.readEntry("italic", defaultLook.italic()); bool bold = config.readEntry("bold", defaultLook.bold()); QString underliningString = config.readEntry("underlining", defaultUnderliningString); QColor color = config.readEntry("color", defaultLook.color()); QColor hoverColor = config.readEntry("hoverColor", defaultLook.hoverColor()); int iconSize = config.readEntry("iconSize", defaultLook.iconSize()); QString previewString = config.readEntry("preview", defaultPreviewString); int underlining = 0; if (underliningString == underliningStrings[1]) underlining = 1; else if (underliningString == underliningStrings[2]) underlining = 2; else if (underliningString == underliningStrings[3]) underlining = 3; int preview = 0; if (previewString == previewStrings[1]) preview = 1; else if (previewString == previewStrings[2]) preview = 2; else if (previewString == previewStrings[3]) preview = 3; look->setLook(italic, bold, underlining, color, hoverColor, iconSize, preview); } void Settings::saveLinkLook(LinkLook *look, const QString &name) { KConfigGroup config = Global::config()->group(name); QString underliningStrings[] = {"Always", "Never", "OnMouseHover", "OnMouseOutside"}; QString underliningString = underliningStrings[look->underlining()]; QString previewStrings[] = {"None", "IconSize", "TwiceIconSize", "ThreeIconSize"}; QString previewString = previewStrings[look->preview()]; config.writeEntry("italic", look->italic()); config.writeEntry("bold", look->bold()); config.writeEntry("underlining", underliningString); config.writeEntry("color", look->color()); config.writeEntry("hoverColor", look->hoverColor()); config.writeEntry("iconSize", look->iconSize()); config.writeEntry("preview", previewString); } void Settings::setBigNotes(bool big) { if (big == s_bigNotes) return; s_bigNotes = big; // Big notes for accessibility reasons OR Standard small notes: Note::NOTE_MARGIN = (big ? 4 : 2); Note::INSERTION_HEIGHT = (big ? 5 : 3); Note::EXPANDER_WIDTH = 9; Note::EXPANDER_HEIGHT = 9; Note::GROUP_WIDTH = 2 * Note::NOTE_MARGIN + Note::EXPANDER_WIDTH; Note::HANDLE_WIDTH = Note::GROUP_WIDTH; Note::RESIZER_WIDTH = Note::GROUP_WIDTH; Note::TAG_ARROW_WIDTH = 5 + (big ? 4 : 0); Note::EMBLEM_SIZE = 16; Note::MIN_HEIGHT = 2 * Note::NOTE_MARGIN + Note::EMBLEM_SIZE; if (Global::bnpView) Global::bnpView->relayoutAllBaskets(); } void Settings::setAutoBullet(bool yes) { s_autoBullet = yes; if (Global::bnpView && Global::bnpView->currentBasket()) { Global::bnpView->currentBasket()->editorPropertiesChanged(); } } /** GeneralPage */ GeneralPage::GeneralPage(QWidget *parent, const char *name) : KCModule(parent) { KAboutData *about = new AboutData(); about->setComponentName(name); setAboutData(about); QVBoxLayout *layout = new QVBoxLayout(this); QHBoxLayout *hLay; QLabel *label; HelpLabel *hLabel; QGridLayout *gl = new QGridLayout; layout->addLayout(gl); gl->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding), 0, 2); // Basket Tree Position: m_treeOnLeft = new KComboBox(this); m_treeOnLeft->addItem(i18n("On left")); m_treeOnLeft->addItem(i18n("On right")); label = new QLabel(this); label->setText(i18n("&Basket tree position:")); label->setBuddy(m_treeOnLeft); gl->addWidget(label, 0, 0); gl->addWidget(m_treeOnLeft, 0, 1); connect(m_treeOnLeft, SIGNAL(activated(int)), this, SLOT(changed())); // Filter Bar Position: m_filterOnTop = new KComboBox(this); m_filterOnTop->addItem(i18n("On top")); m_filterOnTop->addItem(i18n("On bottom")); label = new QLabel(this); label->setText(i18n("&Filter bar position:")); label->setBuddy(m_filterOnTop); gl->addWidget(label, 1, 0); gl->addWidget(m_filterOnTop, 1, 1); connect(m_filterOnTop, SIGNAL(activated(int)), this, SLOT(changed())); // Use balloons to Report Results of Global Actions: hLay = new QHBoxLayout(0); m_usePassivePopup = new QCheckBox(i18n("&Use balloons to report results of global actions"), this); connect(m_usePassivePopup, SIGNAL(stateChanged(int)), this, SLOT(changed())); hLabel = new HelpLabel(i18n("What are global actions?"), ("

    " + i18n("You can configure global shortcuts to do some actions without having to show the main window. For instance, you can paste the clipboard content, take a color from " "a point of the screen, etc. You can also use the mouse scroll wheel over the system tray icon to change the current basket. Or use the middle mouse button " "on that icon to paste the current selection.") + "

    " + "

    " + i18n("When doing so, %1 pops up a little balloon message to inform you the action has been successfully done. You can disable that balloon.", QGuiApplication::applicationDisplayName()) + "

    " + "

    " + i18n("Note that those messages are smart enough to not appear if the main window is visible. This is because you already see the result of your actions in the main window.") + "

    "), this); hLay->addWidget(m_usePassivePopup); hLay->addWidget(hLabel); hLay->addStretch(); layout->addLayout(hLay); // System Tray Icon: QGroupBox *gbSys = new QGroupBox(i18n("System Tray Icon"), this); layout->addWidget(gbSys); QVBoxLayout *sysLay = new QVBoxLayout(gbSys); // Dock in System Tray: m_useSystray = new QCheckBox(i18n("&Dock in system tray"), gbSys); sysLay->addWidget(m_useSystray); connect(m_useSystray, SIGNAL(stateChanged(int)), this, SLOT(changed())); m_systray = new QWidget(gbSys); QVBoxLayout *subSysLay = new QVBoxLayout(m_systray); sysLay->addWidget(m_systray); // Show Current Basket Icon in System Tray Icon: m_showIconInSystray = new QCheckBox(i18n("&Show current basket icon in system tray icon"), m_systray); subSysLay->addWidget(m_showIconInSystray); connect(m_showIconInSystray, SIGNAL(stateChanged(int)), this, SLOT(changed())); QGridLayout *gs = new QGridLayout; subSysLay->addLayout(gs); gs->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding), 0, 2); // Hide Main Window when Mouse Goes out of it for Some Time: m_timeToHideOnMouseOut = new QSpinBox(); m_hideOnMouseOut = new QCheckBox(i18n("&Hide main window when mouse leaves it for"), m_systray); m_timeToHideOnMouseOut->setRange(0, 600); m_timeToHideOnMouseOut->setSuffix(i18n(" tenths of seconds")); gs->addWidget(m_hideOnMouseOut, 0, 0); gs->addWidget(m_timeToHideOnMouseOut, 0, 1); connect(m_hideOnMouseOut, SIGNAL(stateChanged(int)), this, SLOT(changed())); connect(m_timeToHideOnMouseOut, SIGNAL(valueChanged(int)), this, SLOT(changed())); // subSysLay->addWidget( // Show Main Window when Mouse Hovers over the System Tray Icon for Some Time: m_timeToShowOnMouseIn = new QSpinBox(); m_showOnMouseIn = new QCheckBox(i18n("Show &main window when mouse hovers over the system tray icon for"), m_systray); m_timeToShowOnMouseIn->setRange(0, 600); m_timeToShowOnMouseIn->setSuffix(i18n(" tenths of seconds")); gs->addWidget(m_showOnMouseIn, 1, 0); gs->addWidget(m_timeToShowOnMouseIn, 1, 1); connect(m_showOnMouseIn, SIGNAL(stateChanged(int)), this, SLOT(changed())); connect(m_timeToShowOnMouseIn, SIGNAL(valueChanged(int)), this, SLOT(changed())); connect(m_hideOnMouseOut, SIGNAL(toggled(bool)), m_timeToHideOnMouseOut, SLOT(setEnabled(bool))); connect(m_showOnMouseIn, SIGNAL(toggled(bool)), m_timeToShowOnMouseIn, SLOT(setEnabled(bool))); connect(m_useSystray, SIGNAL(toggled(bool)), m_systray, SLOT(setEnabled(bool))); layout->insertStretch(-1); load(); } void GeneralPage::load() { m_treeOnLeft->setCurrentIndex((int)!Settings::treeOnLeft()); m_filterOnTop->setCurrentIndex((int)!Settings::filterOnTop()); m_usePassivePopup->setChecked(Settings::usePassivePopup()); m_useSystray->setChecked(Settings::useSystray()); m_systray->setEnabled(Settings::useSystray()); m_showIconInSystray->setChecked(Settings::showIconInSystray()); m_hideOnMouseOut->setChecked(Settings::hideOnMouseOut()); m_timeToHideOnMouseOut->setValue(Settings::timeToHideOnMouseOut()); m_timeToHideOnMouseOut->setEnabled(Settings::hideOnMouseOut()); m_showOnMouseIn->setChecked(Settings::showOnMouseIn()); m_timeToShowOnMouseIn->setValue(Settings::timeToShowOnMouseIn()); m_timeToShowOnMouseIn->setEnabled(Settings::showOnMouseIn()); } void GeneralPage::save() { Settings::setTreeOnLeft(!m_treeOnLeft->currentIndex()); Settings::setFilterOnTop(!m_filterOnTop->currentIndex()); Settings::setUsePassivePopup(m_usePassivePopup->isChecked()); Settings::setUseSystray(m_useSystray->isChecked()); Settings::setShowIconInSystray(m_showIconInSystray->isChecked()); Settings::setShowOnMouseIn(m_showOnMouseIn->isChecked()); Settings::setTimeToShowOnMouseIn(m_timeToShowOnMouseIn->value()); Settings::setHideOnMouseOut(m_hideOnMouseOut->isChecked()); Settings::setTimeToHideOnMouseOut(m_timeToHideOnMouseOut->value()); } void GeneralPage::defaults() { // TODO } /** BasketsPage */ BasketsPage::BasketsPage(QWidget *parent, const char *name) : KCModule(parent) { KAboutData *about = new AboutData(); about->setComponentName(name); setAboutData(about); QVBoxLayout *layout = new QVBoxLayout(this); QHBoxLayout *hLay; HelpLabel *hLabel; // Appearance: QGroupBox *appearanceBox = new QGroupBox(i18n("Appearance"), this); QVBoxLayout *appearanceLayout = new QVBoxLayout; appearanceBox->setLayout(appearanceLayout); layout->addWidget(appearanceBox); m_playAnimations = new QCheckBox(i18n("Ani&mate changes in baskets"), appearanceBox); appearanceLayout->addWidget(m_playAnimations); connect(m_playAnimations, SIGNAL(stateChanged(int)), this, SLOT(changed())); m_showNotesToolTip = new QCheckBox(i18n("&Show tooltips in baskets"), appearanceBox); appearanceLayout->addWidget(m_showNotesToolTip); connect(m_showNotesToolTip, SIGNAL(stateChanged(int)), this, SLOT(changed())); m_bigNotes = new QCheckBox(i18n("&Big notes"), appearanceBox); appearanceLayout->addWidget(m_bigNotes); connect(m_bigNotes, SIGNAL(stateChanged(int)), this, SLOT(changed())); // Behavior: QGroupBox *behaviorBox = new QGroupBox(i18n("Behavior"), this); QVBoxLayout *behaviorLayout = new QVBoxLayout; behaviorBox->setLayout(behaviorLayout); layout->addWidget(behaviorBox); m_autoBullet = new QCheckBox(i18n("&Transform lines starting with * or - to lists in text editors"), behaviorBox); behaviorLayout->addWidget(m_autoBullet); connect(m_autoBullet, SIGNAL(stateChanged(int)), this, SLOT(changed())); m_confirmNoteDeletion = new QCheckBox(i18n("Ask confirmation before &deleting notes"), behaviorBox); behaviorLayout->addWidget(m_confirmNoteDeletion); connect(m_confirmNoteDeletion, SIGNAL(stateChanged(int)), this, SLOT(changed())); m_pasteAsPlainText = new QCheckBox(i18n("Do not keep text formatting when pasting"), behaviorBox); behaviorLayout->addWidget(m_pasteAsPlainText); connect(m_pasteAsPlainText, SIGNAL(stateChanged(int)), this, SLOT(changed())); QWidget *widget = new QWidget(behaviorBox); behaviorLayout->addWidget(widget); hLay = new QHBoxLayout(widget); m_exportTextTags = new QCheckBox(i18n("&Export tags in texts"), widget); connect(m_exportTextTags, SIGNAL(stateChanged(int)), this, SLOT(changed())); hLabel = new HelpLabel(i18n("When does this apply?"), "

    " + i18n("It does apply when you copy and paste, or drag and drop notes to a text editor.") + "

    " + "

    " + i18n("If enabled, this property lets you paste the tags as textual equivalents.") + "
    " + i18n("For instance, a list of notes with the To Do and Done tags are exported as lines preceded by [ ] or [x], " "representing an empty checkbox and a checked box.") + "

    " + "

    ", widget); hLay->addWidget(m_exportTextTags); hLay->addWidget(hLabel); hLay->addStretch(); m_groupOnInsertionLineWidget = new QWidget(behaviorBox); behaviorLayout->addWidget(m_groupOnInsertionLineWidget); QHBoxLayout *hLayV = new QHBoxLayout(m_groupOnInsertionLineWidget); m_groupOnInsertionLine = new QCheckBox(i18n("&Group a new note when clicking on the right of the insertion line"), m_groupOnInsertionLineWidget); HelpLabel *helpV = new HelpLabel(i18n("How to group a new note?"), i18n("

    When this option is enabled, the insertion-line not only allows you to insert notes at the cursor position, but also allows you to group a new note with the one under the cursor:

    ") + "

    " + i18n("

    Place your mouse between notes, where you want to add a new one.
    " "Click on the left of the insertion-line middle-mark to insert a note.
    " "Click on the right to group a note, with the one below or above, depending on where your mouse is.

    "), m_groupOnInsertionLineWidget); hLayV->addWidget(m_groupOnInsertionLine); hLayV->addWidget(helpV); hLayV->insertStretch(-1); layout->addWidget(m_groupOnInsertionLineWidget); connect(m_groupOnInsertionLine, SIGNAL(stateChanged(int)), this, SLOT(changed())); widget = new QWidget(behaviorBox); behaviorLayout->addWidget(widget); QGridLayout *ga = new QGridLayout(widget); ga->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding), 0, 3); m_middleAction = new KComboBox(widget); m_middleAction->addItem(i18n("Do nothing")); m_middleAction->addItem(i18n("Paste clipboard")); m_middleAction->addItem(i18n("Insert image note")); m_middleAction->addItem(i18n("Insert link note")); m_middleAction->addItem(i18n("Insert cross reference")); m_middleAction->addItem(i18n("Insert launcher note")); m_middleAction->addItem(i18n("Insert color note")); m_middleAction->addItem(i18n("Grab screen zone")); m_middleAction->addItem(i18n("Insert color from screen")); m_middleAction->addItem(i18n("Load note from file")); m_middleAction->addItem(i18n("Import Launcher for desktop application")); m_middleAction->addItem(i18n("Import icon")); QLabel *labelM = new QLabel(widget); labelM->setText(i18n("&Shift+middle-click anywhere:")); labelM->setBuddy(m_middleAction); ga->addWidget(labelM, 0, 0); ga->addWidget(m_middleAction, 0, 1); ga->addWidget(new QLabel(i18n("at cursor position"), widget), 0, 2); connect(m_middleAction, SIGNAL(activated(int)), this, SLOT(changed())); // Protection: QGroupBox *protectionBox = new QGroupBox(i18n("Password Protection"), this); QVBoxLayout *protectionLayout = new QVBoxLayout; layout->addWidget(protectionBox); protectionBox->setLayout(protectionLayout); widget = new QWidget(protectionBox); protectionLayout->addWidget(widget); // Re-Lock timeout configuration hLay = new QHBoxLayout(widget); m_enableReLockTimeoutMinutes = new QCheckBox(i18n("A&utomatically lock protected baskets when closed for"), widget); hLay->addWidget(m_enableReLockTimeoutMinutes); m_reLockTimeoutMinutes = new QSpinBox(widget); m_reLockTimeoutMinutes->setMinimum(0); m_reLockTimeoutMinutes->setSuffix(i18n(" minutes")); hLay->addWidget(m_reLockTimeoutMinutes); // label = new QLabel(i18n("minutes"), this); // hLay->addWidget(label); hLay->addStretch(); // layout->addLayout(hLay); connect(m_enableReLockTimeoutMinutes, SIGNAL(stateChanged(int)), this, SLOT(changed())); connect(m_reLockTimeoutMinutes, SIGNAL(valueChanged(int)), this, SLOT(changed())); connect(m_enableReLockTimeoutMinutes, SIGNAL(toggled(bool)), m_reLockTimeoutMinutes, SLOT(setEnabled(bool))); #ifdef HAVE_LIBGPGME m_useGnuPGAgent = new QCheckBox(i18n("Use GnuPG agent for &private/public key protected baskets"), protectionBox); protectionLayout->addWidget(m_useGnuPGAgent); // hLay->addWidget(m_useGnuPGAgent); connect(m_useGnuPGAgent, SIGNAL(stateChanged(int)), this, SLOT(changed())); #endif layout->insertStretch(-1); load(); } void BasketsPage::load() { m_playAnimations->setChecked(Settings::playAnimations()); m_showNotesToolTip->setChecked(Settings::showNotesToolTip()); m_bigNotes->setChecked(Settings::bigNotes()); m_autoBullet->setChecked(Settings::autoBullet()); m_confirmNoteDeletion->setChecked(Settings::confirmNoteDeletion()); m_pasteAsPlainText->setChecked(Settings::pasteAsPlainText()); m_exportTextTags->setChecked(Settings::exportTextTags()); m_groupOnInsertionLine->setChecked(Settings::groupOnInsertionLine()); m_middleAction->setCurrentIndex(Settings::middleAction()); // The correctness of this code depends on the default of enableReLockTimeout // being true - otherwise, the reLockTimeoutMinutes widget is not disabled properly. m_enableReLockTimeoutMinutes->setChecked(Settings::enableReLockTimeout()); m_reLockTimeoutMinutes->setValue(Settings::reLockTimeoutMinutes()); #ifdef HAVE_LIBGPGME m_useGnuPGAgent->setChecked(Settings::useGnuPGAgent()); if (KGpgMe::isGnuPGAgentAvailable()) { m_useGnuPGAgent->setChecked(Settings::useGnuPGAgent()); } else { m_useGnuPGAgent->setChecked(false); m_useGnuPGAgent->setEnabled(false); } #endif } void BasketsPage::save() { Settings::setPlayAnimations(m_playAnimations->isChecked()); Settings::setShowNotesToolTip(m_showNotesToolTip->isChecked()); Settings::setBigNotes(m_bigNotes->isChecked()); Settings::setAutoBullet(m_autoBullet->isChecked()); Settings::setConfirmNoteDeletion(m_confirmNoteDeletion->isChecked()); Settings::setPasteAsPlainText(m_pasteAsPlainText->isChecked()); Settings::setExportTextTags(m_exportTextTags->isChecked()); Settings::setGroupOnInsertionLine(m_groupOnInsertionLine->isChecked()); Settings::setMiddleAction(m_middleAction->currentIndex()); Settings::setEnableReLockTimeout(m_enableReLockTimeoutMinutes->isChecked()); Settings::setReLockTimeoutMinutes(m_reLockTimeoutMinutes->value()); #ifdef HAVE_LIBGPGME Settings::setUseGnuPGAgent(m_useGnuPGAgent->isChecked()); #endif } void BasketsPage::defaults() { // TODO } /** class NewNotesPage: */ NewNotesPage::NewNotesPage(QWidget *parent, const char *name) : KCModule(parent) { KAboutData *about = new AboutData(); about->setComponentName(name); setAboutData(about); QVBoxLayout *layout = new QVBoxLayout(this); QHBoxLayout *hLay; QLabel *label; // Place of New Notes: hLay = new QHBoxLayout; m_newNotesPlace = new KComboBox(this); label = new QLabel(this); label->setText(i18n("&Place of new notes:")); label->setBuddy(m_newNotesPlace); m_newNotesPlace->addItem(i18n("On top")); m_newNotesPlace->addItem(i18n("On bottom")); m_newNotesPlace->addItem(i18n("At current note")); hLay->addWidget(label); hLay->addWidget(m_newNotesPlace); hLay->addStretch(); // layout->addLayout(hLay); label->hide(); m_newNotesPlace->hide(); connect(m_newNotesPlace, SIGNAL(editTextChanged(const QString &)), this, SLOT(changed())); // New Images Size: hLay = new QHBoxLayout; m_imgSizeX = new QSpinBox(this); m_imgSizeX->setMinimum(1); m_imgSizeX->setMaximum(4096); // m_imgSizeX->setReferencePoint(100); //from KIntNumInput connect(m_imgSizeX, SIGNAL(valueChanged(int)), this, SLOT(changed())); label = new QLabel(this); label->setText(i18n("&New images size:")); label->setBuddy(m_imgSizeX); hLay->addWidget(label); hLay->addWidget(m_imgSizeX); m_imgSizeY = new QSpinBox(this); m_imgSizeY->setMinimum(1); m_imgSizeY->setMaximum(4096); // m_imgSizeY->setReferencePoint(100); connect(m_imgSizeY, SIGNAL(valueChanged(int)), this, SLOT(changed())); label = new QLabel(this); label->setText(i18n("&by")); label->setBuddy(m_imgSizeY); hLay->addWidget(label); hLay->addWidget(m_imgSizeY); label = new QLabel(i18n("pixels"), this); hLay->addWidget(label); m_pushVisualize = new QPushButton(i18n("&Visualize..."), this); hLay->addWidget(m_pushVisualize); hLay->addStretch(); layout->addLayout(hLay); connect(m_pushVisualize, SIGNAL(clicked()), this, SLOT(visualize())); // View File Content: QGroupBox *buttonGroup = new QGroupBox(i18n("View Content of Added Files for the Following Types"), this); QVBoxLayout *buttonLayout = new QVBoxLayout; m_viewTextFileContent = new QCheckBox(i18n("&Plain text"), buttonGroup); m_viewHtmlFileContent = new QCheckBox(i18n("&HTML page"), buttonGroup); m_viewImageFileContent = new QCheckBox(i18n("&Image or animation"), buttonGroup); m_viewSoundFileContent = new QCheckBox(i18n("&Sound"), buttonGroup); buttonLayout->addWidget(m_viewTextFileContent); buttonLayout->addWidget(m_viewHtmlFileContent); buttonLayout->addWidget(m_viewImageFileContent); buttonLayout->addWidget(m_viewSoundFileContent); buttonGroup->setLayout(buttonLayout); layout->addWidget(buttonGroup); connect(m_viewTextFileContent, SIGNAL(stateChanged(int)), this, SLOT(changed())); connect(m_viewHtmlFileContent, SIGNAL(stateChanged(int)), this, SLOT(changed())); connect(m_viewImageFileContent, SIGNAL(stateChanged(int)), this, SLOT(changed())); connect(m_viewSoundFileContent, SIGNAL(stateChanged(int)), this, SLOT(changed())); layout->insertStretch(-1); load(); } void NewNotesPage::load() { m_newNotesPlace->setCurrentIndex(Settings::newNotesPlace()); m_imgSizeX->setValue(Settings::defImageX()); m_imgSizeY->setValue(Settings::defImageY()); m_viewTextFileContent->setChecked(Settings::viewTextFileContent()); m_viewHtmlFileContent->setChecked(Settings::viewHtmlFileContent()); m_viewImageFileContent->setChecked(Settings::viewImageFileContent()); m_viewSoundFileContent->setChecked(Settings::viewSoundFileContent()); } void NewNotesPage::save() { Settings::setNewNotesPlace(m_newNotesPlace->currentIndex()); Settings::setDefImageX(m_imgSizeX->value()); Settings::setDefImageY(m_imgSizeY->value()); Settings::setViewTextFileContent(m_viewTextFileContent->isChecked()); Settings::setViewHtmlFileContent(m_viewHtmlFileContent->isChecked()); Settings::setViewImageFileContent(m_viewImageFileContent->isChecked()); Settings::setViewSoundFileContent(m_viewSoundFileContent->isChecked()); } void NewNotesPage::defaults() { // TODO } void NewNotesPage::visualize() { QPointer size = new ViewSizeDialog(this, m_imgSizeX->value(), m_imgSizeY->value()); size->exec(); m_imgSizeX->setValue(size->width()); m_imgSizeY->setValue(size->height()); } /** class NotesAppearancePage: */ NotesAppearancePage::NotesAppearancePage(QWidget *parent, const char *name) : KCModule(parent) { KAboutData *about = new AboutData(); about->setComponentName(name); setAboutData(about); QVBoxLayout *layout = new QVBoxLayout(this); QTabWidget *tabs = new QTabWidget(this); layout->addWidget(tabs); m_soundLook = new LinkLookEditWidget(this, i18n("Conference audio record"), "folder-sound", tabs); m_fileLook = new LinkLookEditWidget(this, i18n("Annual report"), "folder-documents", tabs); m_localLinkLook = new LinkLookEditWidget(this, i18n("Home folder"), "user-home", tabs); m_networkLinkLook = new LinkLookEditWidget(this, "kde.org", KIO::iconNameForUrl(QUrl("https://kde.org")), tabs); m_launcherLook = new LinkLookEditWidget(this, i18n("Launch %1", QGuiApplication::applicationDisplayName()), "basket", tabs); m_crossReferenceLook = new LinkLookEditWidget(this, i18n("Another basket"), "basket", tabs); tabs->addTab(m_soundLook, i18n("&Sounds")); tabs->addTab(m_fileLook, i18n("&Files")); tabs->addTab(m_localLinkLook, i18n("&Local Links")); tabs->addTab(m_networkLinkLook, i18n("&Network Links")); tabs->addTab(m_launcherLook, i18n("Launc&hers")); tabs->addTab(m_crossReferenceLook, i18n("&Cross References")); load(); } void NotesAppearancePage::load() { m_soundLook->set(LinkLook::soundLook); m_fileLook->set(LinkLook::fileLook); m_localLinkLook->set(LinkLook::localLinkLook); m_networkLinkLook->set(LinkLook::networkLinkLook); m_launcherLook->set(LinkLook::launcherLook); m_crossReferenceLook->set(LinkLook::crossReferenceLook); } void NotesAppearancePage::save() { m_soundLook->saveChanges(); m_fileLook->saveChanges(); m_localLinkLook->saveChanges(); m_networkLinkLook->saveChanges(); m_launcherLook->saveChanges(); m_crossReferenceLook->saveChanges(); Global::bnpView->linkLookChanged(); } void NotesAppearancePage::defaults() { // TODO } /** class ApplicationsPage: */ ApplicationsPage::ApplicationsPage(QWidget *parent, const char *name) : KCModule(parent) { KAboutData *about = new AboutData(); about->setComponentName(name); setAboutData(about); /* Applications page */ QVBoxLayout *layout = new QVBoxLayout(this); m_htmlUseProg = new QCheckBox(i18n("Open &text notes with a custom application:"), this); m_htmlProg = new RunCommandRequester("", i18n("Open text notes with:"), this); QHBoxLayout *hLayH = new QHBoxLayout; hLayH->insertSpacing(-1, 20); hLayH->addWidget(m_htmlProg); connect(m_htmlUseProg, SIGNAL(stateChanged(int)), this, SLOT(changed())); connect(m_htmlProg->lineEdit(), SIGNAL(textChanged(const QString &)), this, SLOT(changed())); m_imageUseProg = new QCheckBox(i18n("Open &image notes with a custom application:"), this); m_imageProg = new RunCommandRequester("", i18n("Open image notes with:"), this); QHBoxLayout *hLayI = new QHBoxLayout; hLayI->insertSpacing(-1, 20); hLayI->addWidget(m_imageProg); connect(m_imageUseProg, SIGNAL(stateChanged(int)), this, SLOT(changed())); connect(m_imageProg->lineEdit(), SIGNAL(textChanged(const QString &)), this, SLOT(changed())); m_animationUseProg = new QCheckBox(i18n("Open a&nimation notes with a custom application:"), this); m_animationProg = new RunCommandRequester("", i18n("Open animation notes with:"), this); QHBoxLayout *hLayA = new QHBoxLayout; hLayA->insertSpacing(-1, 20); hLayA->addWidget(m_animationProg); connect(m_animationUseProg, SIGNAL(stateChanged(int)), this, SLOT(changed())); connect(m_animationProg->lineEdit(), SIGNAL(textChanged(const QString &)), this, SLOT(changed())); m_soundUseProg = new QCheckBox(i18n("Open so&und notes with a custom application:"), this); m_soundProg = new RunCommandRequester("", i18n("Open sound notes with:"), this); QHBoxLayout *hLayS = new QHBoxLayout; hLayS->insertSpacing(-1, 20); hLayS->addWidget(m_soundProg); connect(m_soundUseProg, SIGNAL(stateChanged(int)), this, SLOT(changed())); connect(m_soundProg->lineEdit(), SIGNAL(textChanged(const QString &)), this, SLOT(changed())); QString whatsthis = i18n( "

    If checked, the application defined below will be used when opening that type of note.

    " "

    Otherwise, the application you've configured in Konqueror will be used.

    "); m_htmlUseProg->setWhatsThis(whatsthis); m_imageUseProg->setWhatsThis(whatsthis); m_animationUseProg->setWhatsThis(whatsthis); m_soundUseProg->setWhatsThis(whatsthis); whatsthis = i18n( "

    Define the application to use for opening that type of note instead of the " "application configured in Konqueror.

    "); m_htmlProg->setWhatsThis(whatsthis); m_imageProg->setWhatsThis(whatsthis); m_animationProg->setWhatsThis(whatsthis); m_soundProg->setWhatsThis(whatsthis); layout->addWidget(m_htmlUseProg); layout->addItem(hLayH); layout->addWidget(m_imageUseProg); layout->addItem(hLayI); layout->addWidget(m_animationUseProg); layout->addItem(hLayA); layout->addWidget(m_soundUseProg); layout->addItem(hLayS); QHBoxLayout *hLay = new QHBoxLayout; HelpLabel *hl1 = new HelpLabel(i18n("How to change the application used to open Web links?"), i18n("

    When opening Web links, they are opened in different applications, depending on the content of the link " "(a Web page, an image, a PDF document...), such as if they were files on your computer.

    " "

    Here is how to do if you want every Web addresses to be opened in your Web browser. " "It is useful if you are not using Plasma (if you are using eg. GNOME, XFCE...).

    " "
      " "
    • Open the KDE System Settings (if it is not available, try to type \"systemsettings\" in a command line terminal);
    • " "
    • Go to the \"Applications\" and then \"Default Applications\" section;
    • " "
    • Choose \"Web Browser\", check \"with the following command:\" and enter the name of your Web browser (like \"firefox\" or \"epiphany\").
    • " "
    " "

    Now, when you click any link that start with \"https://...\", it will be opened in your Web browser (eg. Mozilla Firefox or Epiphany or...).

    " "

    For more fine-grained configuration (like opening only Web pages in your Web browser), read the second help link.

    "), this); hLay->addWidget(hl1); hLay->addStretch(); layout->addLayout(hLay); hLay = new QHBoxLayout; HelpLabel *hl2 = new HelpLabel(i18n("How to change the applications used to open files and links?"), i18n("

    Here is how to set the application to be used for each type of file. " "This also applies to Web links if you choose not to open them systematically in a Web browser (see the first help link). " "The default settings should be good enough for you, but this tip is useful if you are using GNOME, XFCE, or another environment than Plasma.

    " "

    This is an example of how to open HTML pages in your Web browser (and keep using the other applications for other addresses or files). " "Repeat these steps for each type of file you want to open in a specific application.

    " "
      " "
    • Open the KDE System Settings (if it is not available, try to type \"systemsettings\" in a command line terminal);
    • " "
    • Go to the \"Applications\" and then \"File Associations\" section;
    • " "
    • In the tree, expand \"text\" and click \"html\";
    • " "
    • In the applications list, add your Web browser as the first entry;
    • " "
    • Do the same for the type \"application -> xhtml+xml\".
    • " "
    "), this); hLay->addWidget(hl2); hLay->addStretch(); layout->addLayout(hLay); connect(m_htmlUseProg, SIGNAL(toggled(bool)), m_htmlProg, SLOT(setEnabled(bool))); connect(m_imageUseProg, SIGNAL(toggled(bool)), m_imageProg, SLOT(setEnabled(bool))); connect(m_animationUseProg, SIGNAL(toggled(bool)), m_animationProg, SLOT(setEnabled(bool))); connect(m_soundUseProg, SIGNAL(toggled(bool)), m_soundProg, SLOT(setEnabled(bool))); layout->insertStretch(-1); load(); } void ApplicationsPage::load() { m_htmlProg->setRunCommand(Settings::htmlProg()); m_htmlUseProg->setChecked(Settings::isHtmlUseProg()); m_htmlProg->setEnabled(Settings::isHtmlUseProg()); m_imageProg->setRunCommand(Settings::imageProg()); m_imageUseProg->setChecked(Settings::isImageUseProg()); m_imageProg->setEnabled(Settings::isImageUseProg()); m_animationProg->setRunCommand(Settings::animationProg()); m_animationUseProg->setChecked(Settings::isAnimationUseProg()); m_animationProg->setEnabled(Settings::isAnimationUseProg()); m_soundProg->setRunCommand(Settings::soundProg()); m_soundUseProg->setChecked(Settings::isSoundUseProg()); m_soundProg->setEnabled(Settings::isSoundUseProg()); } void ApplicationsPage::save() { Settings::setIsHtmlUseProg(m_htmlUseProg->isChecked()); Settings::setHtmlProg(m_htmlProg->runCommand()); Settings::setIsImageUseProg(m_imageUseProg->isChecked()); Settings::setImageProg(m_imageProg->runCommand()); Settings::setIsAnimationUseProg(m_animationUseProg->isChecked()); Settings::setAnimationProg(m_animationProg->runCommand()); Settings::setIsSoundUseProg(m_soundUseProg->isChecked()); Settings::setSoundProg(m_soundProg->runCommand()); } void ApplicationsPage::defaults() { // TODO } diff --git a/src/settings.h b/src/settings.h index bfbf05a..7784ebd 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,661 +1,648 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef SETTINGS_H #define SETTINGS_H #include #include //For Global::mainWindow() #include //For UseSysTray #include #include "basket_export.h" #include "bnpview.h" #include "systemtray.h" class KComboBox; class QString; class QCheckBox; class QPushButton; class QPoint; class QSize; class QSpinBox; class LinkLook; class LinkLookEditWidget; class RunCommandRequester; class BASKET_EXPORT GeneralPage : public KCModule { Q_OBJECT public: explicit GeneralPage(QWidget *parent = 0, const char *name = 0); ~GeneralPage() override { } void load() override; void save() override; void defaults() override; private: // General KComboBox *m_treeOnLeft; KComboBox *m_filterOnTop; QCheckBox *m_usePassivePopup; // System Tray Icon QCheckBox *m_useSystray; QWidget *m_systray; QCheckBox *m_showIconInSystray; QCheckBox *m_hideOnMouseOut; QSpinBox *m_timeToHideOnMouseOut; QCheckBox *m_showOnMouseIn; QSpinBox *m_timeToShowOnMouseIn; }; class BASKET_EXPORT BasketsPage : public KCModule { Q_OBJECT public: explicit BasketsPage(QWidget *parent = 0, const char *name = 0); void load() override; void save() override; void defaults() override; private: // Appearance QCheckBox *m_playAnimations; QCheckBox *m_showNotesToolTip; QCheckBox *m_bigNotes; // Behavior QCheckBox *m_autoBullet; QCheckBox *m_confirmNoteDeletion; QCheckBox *m_exportTextTags; QWidget *m_groupOnInsertionLineWidget; QCheckBox *m_groupOnInsertionLine; KComboBox *m_middleAction; QCheckBox *m_pasteAsPlainText; // Protection QCheckBox *m_useGnuPGAgent; QCheckBox *m_enableReLockTimeoutMinutes; QSpinBox *m_reLockTimeoutMinutes; }; class BASKET_EXPORT NewNotesPage : public KCModule { Q_OBJECT public: explicit NewNotesPage(QWidget *parent = 0, const char *name = 0); void load() override; void save() override; void defaults() override; private slots: void visualize(); private: // Notes Image Size QSpinBox *m_imgSizeX; QSpinBox *m_imgSizeY; QPushButton *m_pushVisualize; // Note Addition KComboBox *m_newNotesPlace; QCheckBox *m_viewTextFileContent; QCheckBox *m_viewHtmlFileContent; QCheckBox *m_viewImageFileContent; QCheckBox *m_viewSoundFileContent; }; class BASKET_EXPORT NotesAppearancePage : public KCModule { Q_OBJECT public: explicit NotesAppearancePage(QWidget *parent = 0, const char *name = 0); void load() override; void save() override; void defaults() override; private: // Link Looks LinkLookEditWidget *m_soundLook; LinkLookEditWidget *m_fileLook; LinkLookEditWidget *m_localLinkLook; LinkLookEditWidget *m_networkLinkLook; LinkLookEditWidget *m_launcherLook; LinkLookEditWidget *m_crossReferenceLook; }; class BASKET_EXPORT ApplicationsPage : public KCModule { Q_OBJECT public: explicit ApplicationsPage(QWidget *parent = 0, const char *name = 0); void load() override; void save() override; void defaults() override; private: // Applications QCheckBox *m_htmlUseProg; QCheckBox *m_imageUseProg; QCheckBox *m_animationUseProg; QCheckBox *m_soundUseProg; RunCommandRequester *m_htmlProg; RunCommandRequester *m_imageProg; RunCommandRequester *m_animationProg; RunCommandRequester *m_soundProg; }; /** Handle all global variables (to avoid lot of extern declarations) * @author Sébastien Laoût */ class BASKET_EXPORT Settings // FIXME: Dispatch new config events ? { protected: /** Main window */ static bool s_treeOnLeft; static bool s_filterOnTop; static bool s_playAnimations; static bool s_showNotesToolTip; static bool s_confirmNoteDeletion; static bool s_bigNotes; static bool s_autoBullet; static bool s_pasteAsPlainText; static bool s_exportTextTags; static bool s_useGnuPGAgent; static bool s_usePassivePopup; static int s_middleAction; // O:Nothing ; 1:Paste ; 2:Text ; 3:Html ; 4:Image ; 5:Link ; 6:Launcher ; 7:Color static bool s_groupOnInsertionLine; static bool s_spellCheckTextNotes; static int s_basketTreeWidth; static bool s_welcomeBasketsAdded; static QString s_dataFolder; static QDate s_lastBackup; static QPoint s_mainWindowPosition; static QSize s_mainWindowSize; static bool s_showEmptyBasketInfo; static bool s_blinkedFilter; static bool s_enableReLockTimeout; static int s_reLockTimeoutMinutes; /** Note Addition */ static int s_newNotesPlace; // 0:OnTop ; 1:OnBottom ; 2:AtCurrentNote static int s_viewTextFileContent; static int s_viewHtmlFileContent; static int s_viewImageFileContent; static int s_viewSoundFileContent; /** System tray Icon */ static bool s_useSystray; static bool s_showIconInSystray; static bool s_startDocked; static bool s_hideOnMouseOut; static int s_timeToHideOnMouseOut; static bool s_showOnMouseIn; static int s_timeToShowOnMouseIn; /** Programs */ static bool s_htmlUseProg; static bool s_imageUseProg; static bool s_animationUseProg; static bool s_soundUseProg; static QString s_htmlProg; static QString s_imageProg; static QString s_animationProg; static QString s_soundProg; /** Insert Note Default Values */ static int s_defImageX; static int s_defImageY; static int s_defIconSize; /** Version Sync */ static bool s_versionSyncEnabled; public: /* And the following methods are just getter / setters */ /** App settings GET */ static inline bool treeOnLeft() { return s_treeOnLeft; } static inline bool filterOnTop() { return s_filterOnTop; } static inline bool playAnimations() { return s_playAnimations; } static inline bool showNotesToolTip() { return s_showNotesToolTip; } static inline bool confirmNoteDeletion() { return s_confirmNoteDeletion; } static inline bool bigNotes() { return s_bigNotes; } static inline bool autoBullet() { return s_autoBullet; } static inline bool pasteAsPlainText() { return s_pasteAsPlainText; } static inline bool exportTextTags() { return s_exportTextTags; } static inline bool useGnuPGAgent() { return s_useGnuPGAgent; } static inline bool blinkedFilter() { return s_blinkedFilter; } static inline bool enableReLockTimeout() { return s_enableReLockTimeout; } static inline int reLockTimeoutMinutes() { return s_reLockTimeoutMinutes; } static inline bool useSystray() { return s_useSystray; } static inline bool showIconInSystray() { return s_showIconInSystray; } static inline bool startDocked() { return s_startDocked; } static inline int middleAction() { return s_middleAction; } static inline bool groupOnInsertionLine() { return s_groupOnInsertionLine; } static inline bool spellCheckTextNotes() { return s_spellCheckTextNotes; } static inline bool hideOnMouseOut() { return s_hideOnMouseOut; } static inline int timeToHideOnMouseOut() { return s_timeToHideOnMouseOut; } static inline bool showOnMouseIn() { return s_showOnMouseIn; } static inline int timeToShowOnMouseIn() { return s_timeToShowOnMouseIn; } static inline int basketTreeWidth() { return s_basketTreeWidth; } static inline int dropTimeToShow() { return 7; } // TODO: 700 ; TODO: There is certainly a KGlobalConfig ??? static inline bool usePassivePopup() { return s_usePassivePopup; } static inline bool welcomeBasketsAdded() { return s_welcomeBasketsAdded; } static inline QString dataFolder() { return s_dataFolder; } static inline QDate lastBackup() { return s_lastBackup; } static inline QPoint mainWindowPosition() { return s_mainWindowPosition; } static inline QSize mainWindowSize() { return s_mainWindowSize; } static inline bool showEmptyBasketInfo() { return s_showEmptyBasketInfo; } /** Programs */ static inline bool isHtmlUseProg() { return s_htmlUseProg; } static inline bool isImageUseProg() { return s_imageUseProg; } static inline bool isAnimationUseProg() { return s_animationUseProg; } static inline bool isSoundUseProg() { return s_soundUseProg; } static inline QString htmlProg() { return s_htmlProg; } static inline QString imageProg() { return s_imageProg; } static inline QString animationProg() { return s_animationProg; } static inline QString soundProg() { return s_soundProg; } /** Insert Note Default Values */ static inline int defImageX() { return s_defImageX; } static inline int defImageY() { return s_defImageY; } static inline int defIconSize() { return s_defIconSize; } /** Note Addition */ static inline int newNotesPlace() { return s_newNotesPlace; } static inline int viewTextFileContent() { return s_viewTextFileContent; } static inline int viewHtmlFileContent() { return s_viewHtmlFileContent; } static inline int viewImageFileContent() { return s_viewImageFileContent; } static inline int viewSoundFileContent() { return s_viewSoundFileContent; } /** Version Sync */ static inline bool versionSyncEnabled() { return s_versionSyncEnabled; } /** App settings SET */ static void setTreeOnLeft(bool onLeft) { s_treeOnLeft = onLeft; if (Global::bnpView) Global::bnpView->setTreePlacement(onLeft); } static void setFilterOnTop(bool onTop) { if (s_filterOnTop != onTop) { s_filterOnTop = onTop; if (Global::bnpView) Global::bnpView->filterPlacementChanged(onTop); } } static void setShowNotesToolTip(bool show) { s_showNotesToolTip = show; } static void setUseSystray(bool useSystray) { if (s_useSystray != useSystray) { s_useSystray = useSystray; if (Global::systemTray != 0L) { if (Settings::useSystray()) Global::systemTray->setStatus(KStatusNotifierItem::Active); else { Global::systemTray->setStatus(KStatusNotifierItem::Passive); if (Global::activeMainWindow()) Global::activeMainWindow()->show(); } } if (Global::bnpView) Global::bnpView->m_actHideWindow->setEnabled(useSystray); } } static void setShowIconInSystray(bool show) { if (s_showIconInSystray != show) s_showIconInSystray = show; } static inline void setConfirmNoteDeletion(bool confirm) { s_confirmNoteDeletion = confirm; } static inline void setPasteAsPlainText(bool yes) { s_pasteAsPlainText = yes; } static void setBigNotes(bool big); static void setAutoBullet(bool yes); static inline void setExportTextTags(bool yes) { s_exportTextTags = yes; } static inline void setUseGnuPGAgent(bool yes) { s_useGnuPGAgent = yes; } static inline void setPlayAnimations(bool play) { s_playAnimations = play; } static inline void setBlinkedFilter(bool blinked) { s_blinkedFilter = blinked; } static inline void setEnableReLockTimeout(bool yes) { s_enableReLockTimeout = yes; } static inline void setReLockTimeoutMinutes(int minutes) { s_reLockTimeoutMinutes = minutes; } static inline void setStartDocked(bool docked) { s_startDocked = docked; } static inline void setMiddleAction(int action) { s_middleAction = action; } static inline void setGroupOnInsertionLine(bool yes) { s_groupOnInsertionLine = yes; } static inline void setSpellCheckTextNotes(bool yes) { s_spellCheckTextNotes = yes; } static inline void setHideOnMouseOut(bool hide) { s_hideOnMouseOut = hide; } static inline void setTimeToHideOnMouseOut(int time) { s_timeToHideOnMouseOut = time; } static inline void setShowOnMouseIn(bool show) { s_showOnMouseIn = show; } static inline void setTimeToShowOnMouseIn(int time) { s_timeToShowOnMouseIn = time; } static inline void setBasketTreeWidth(int width) { s_basketTreeWidth = width; } static inline void setUsePassivePopup(bool enable) { s_usePassivePopup = enable; } static inline void setWelcomeBasketsAdded(bool added) { s_welcomeBasketsAdded = added; } static inline void setDataFolder(const QString &folder) { s_dataFolder = folder; } static inline void setLastBackup(const QDate &date) { s_lastBackup = date; } static inline void setMainWindowPosition(const QPoint &pos) { s_mainWindowPosition = pos; } static inline void setMainWindowSize(const QSize &size) { s_mainWindowSize = size; } static inline void setShowEmptyBasketInfo(bool show) { s_showEmptyBasketInfo = show; } // Programs : static inline void setIsHtmlUseProg(bool useProg) { s_htmlUseProg = useProg; } static inline void setIsImageUseProg(bool useProg) { s_imageUseProg = useProg; } static inline void setIsAnimationUseProg(bool useProg) { s_animationUseProg = useProg; } static inline void setIsSoundUseProg(bool useProg) { s_soundUseProg = useProg; } static inline void setHtmlProg(const QString &prog) { s_htmlProg = prog; } static inline void setImageProg(const QString &prog) { s_imageProg = prog; } static inline void setAnimationProg(const QString &prog) { s_animationProg = prog; } static inline void setSoundProg(const QString &prog) { s_soundProg = prog; } // Insert Note Default Values : static inline void setDefImageX(int val) { s_defImageX = val; } static inline void setDefImageY(int val) { s_defImageY = val; } static inline void setDefIconSize(int val) { s_defIconSize = val; } // Note Addition static inline void setNewNotesPlace(int val) { s_newNotesPlace = val; } static inline void setViewTextFileContent(bool view) { s_viewTextFileContent = view; } static inline void setViewHtmlFileContent(bool view) { s_viewHtmlFileContent = view; } static inline void setViewImageFileContent(bool view) { s_viewImageFileContent = view; } static inline void setViewSoundFileContent(bool view) { s_viewSoundFileContent = view; } // Version Sync static inline void setVersionSyncEnabled(bool enable) { s_versionSyncEnabled = enable; } public: /* Save and load config */ static void loadConfig(); static void saveConfig(); protected: static void loadLinkLook(LinkLook *look, const QString &name, const LinkLook &defaultLook); static void saveLinkLook(LinkLook *look, const QString &name); }; #endif // SETTINGS_H diff --git a/src/settings_versionsync.cpp b/src/settings_versionsync.cpp index cc5537b..4e38a30 100644 --- a/src/settings_versionsync.cpp +++ b/src/settings_versionsync.cpp @@ -1,86 +1,91 @@ +/** + * SPDX-FileCopyrightText: (C) 2016 Gleb Baryshev + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "settings_versionsync.h" #include "aboutdata.h" #include "settings.h" #include "tools.h" #include "ui_settings_versionsync.h" #include #include #include #include #include #include //! Compute size of .git folder and invoke VersionSyncPage::setHistorySize void showHistorySize(QPointer versionSyncPage) { QString git = Global::gitFolder(); qint64 size = QDir(git).exists() ? Tools::computeSizeRecursively(git) : 0; if (!versionSyncPage.isNull()) QMetaObject::invokeMethod(versionSyncPage.data(), "setHistorySize", Qt::QueuedConnection, Q_ARG(qint64, size)); } VersionSyncPage::VersionSyncPage(QWidget *parent, const char *name) : KCModule(parent) , ui(new Ui::VersionSyncPage) { KAboutData *about = new AboutData(); about->setComponentName(name); setAboutData(about); ui->setupUi(this); #ifdef WITH_LIBGIT2 ui->labelWithoutVersionControlSupport->setVisible(false); QtConcurrent::run(showHistorySize, this); #else ui->checkBoxEnable->setEnabled(false); ui->groupBoxControl->setVisible(false); #endif connect(ui->checkBoxEnable, SIGNAL(toggled(bool)), this, SLOT(changed())); load(); } VersionSyncPage::~VersionSyncPage() { delete ui; } void VersionSyncPage::load() { ui->checkBoxEnable->setChecked(Settings::versionSyncEnabled()); #ifdef WITH_LIBGIT2 on_checkBoxEnable_clicked(); #endif } void VersionSyncPage::save() { Settings::setVersionSyncEnabled(ui->checkBoxEnable->isChecked()); } void VersionSyncPage::defaults() { ui->checkBoxEnable->setChecked(false); } void VersionSyncPage::on_checkBoxEnable_clicked() { ui->groupBoxControl->setEnabled(ui->checkBoxEnable->isChecked()); } void VersionSyncPage::on_buttonClearHistory_clicked() { if (KMessageBox::questionYesNo(this, i18n("Do you really want to remove old versions for all baskets?"), i18n("Version Sync")) == KMessageBox::Yes) { Tools::deleteRecursively(Global::gitFolder()); ui->buttonClearHistory->setEnabled(false); setHistorySize(0); Global::initializeGitIfNeeded(Global::savesFolder()); // restore .git } } void VersionSyncPage::setHistorySize(qint64 size_bytes) { QString size_mb = QLocale().toString((float)size_bytes / 1024 / 1024, 'f', 2); ui->labelHistorySize->setText(i18n("This will free %1 MB", size_mb)); } diff --git a/src/settings_versionsync.h b/src/settings_versionsync.h index a2b73f2..6de08b8 100644 --- a/src/settings_versionsync.h +++ b/src/settings_versionsync.h @@ -1,54 +1,41 @@ -/* - * Copyright (C) 2016 by Gleb Baryshev +/** + * SPDX-FileCopyrightText: (C) 2016 Gleb Baryshev * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef SETTINGS_VERSIONSYNC_H #define SETTINGS_VERSIONSYNC_H #include "basket_export.h" #include namespace Ui { class VersionSyncPage; } class BASKET_EXPORT VersionSyncPage : public KCModule { Q_OBJECT public: explicit VersionSyncPage(QWidget *parent = 0, const char *name = 0); ~VersionSyncPage() override; void load() override; void save() override; void defaults() override; public slots: void setHistorySize(qint64 size_bytes); private slots: void on_checkBoxEnable_clicked(); void on_buttonClearHistory_clicked(); private: Ui::VersionSyncPage *ui; }; #endif // SETTINGS_VERSIONSYNC_H diff --git a/src/softwareimporters.cpp b/src/softwareimporters.cpp index a01f866..797b2a1 100644 --- a/src/softwareimporters.cpp +++ b/src/softwareimporters.cpp @@ -1,854 +1,841 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "softwareimporters.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "basketfactory.h" #include "basketscene.h" #include "bnpview.h" #include "debugwindow.h" #include "global.h" #include "icon_names.h" #include "notefactory.h" #include "tools.h" #include "xmlwork.h" /** class TreeImportDialog: */ TreeImportDialog::TreeImportDialog(QWidget *parent) : QDialog(parent) { QWidget *page = new QWidget(this); QVBoxLayout *topLayout = new QVBoxLayout(page); // QDialog options setWindowTitle(i18n("Import Hierarchy")); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); setObjectName("ImportHeirachy"); setModal(true); m_choices = new QGroupBox(i18n("How to Import the Notes?"), page); m_choiceLayout = new QVBoxLayout(); m_choices->setLayout(m_choiceLayout); m_hierarchy_choice = new QRadioButton(i18n("&Keep original hierarchy (all notes in separate baskets)"), m_choices); m_separate_baskets_choice = new QRadioButton(i18n("&First level notes in separate baskets"), m_choices); m_one_basket_choice = new QRadioButton(i18n("&All notes in one basket"), m_choices); m_hierarchy_choice->setChecked(true); m_choiceLayout->addWidget(m_hierarchy_choice); m_choiceLayout->addWidget(m_separate_baskets_choice); m_choiceLayout->addWidget(m_one_basket_choice); topLayout->addWidget(m_choices); topLayout->addStretch(10); mainLayout->addWidget(page); 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, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); } TreeImportDialog::~TreeImportDialog() { } int TreeImportDialog::choice() { if (m_hierarchy_choice->isChecked()) return 1; else if (m_separate_baskets_choice->isChecked()) return 2; else return 3; } /** class TextFileImportDialog: */ TextFileImportDialog::TextFileImportDialog(QWidget *parent) : QDialog(parent) { QWidget *page = new QWidget(this); QVBoxLayout *topLayout = new QVBoxLayout(page); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); // QDialog options setWindowTitle(i18n("Import Text File")); setObjectName("ImportTextFile"); setModal(true); m_choices = new QGroupBox(i18n("Format of the Text File"), page); mainLayout->addWidget(m_choices); m_choiceLayout = new QVBoxLayout; m_choices->setLayout(m_choiceLayout); m_emptyline_choice = new QRadioButton(i18n("Notes separated by an &empty line"), m_choices); m_newline_choice = new QRadioButton(i18n("One ¬e per line"), m_choices); m_dash_choice = new QRadioButton(i18n("Notes begin with a &dash (-)"), m_choices); m_star_choice = new QRadioButton(i18n("Notes begin with a &star (*)"), m_choices); m_anotherSeparator = new QRadioButton(i18n("&Use another separator:"), m_choices); m_choiceLayout->addWidget(m_emptyline_choice); m_choiceLayout->addWidget(m_newline_choice); m_choiceLayout->addWidget(m_dash_choice); m_choiceLayout->addWidget(m_star_choice); m_choiceLayout->addWidget(m_anotherSeparator); QWidget *indentedTextEdit = new QWidget(m_choices); m_choiceLayout->addWidget(indentedTextEdit); QHBoxLayout *hLayout = new QHBoxLayout(indentedTextEdit); hLayout->addSpacing(20); m_customSeparator = new KTextEdit(indentedTextEdit); hLayout->addWidget(m_customSeparator); m_all_in_one_choice = new QRadioButton(i18n("&All in one note"), m_choices); m_choiceLayout->addWidget(m_all_in_one_choice); m_emptyline_choice->setChecked(true); topLayout->addWidget(m_choices); connect(m_customSeparator, SIGNAL(textChanged()), this, SLOT(customSeparatorChanged())); mainLayout->addWidget(page); 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, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); } TextFileImportDialog::~TextFileImportDialog() { } QString TextFileImportDialog::separator() { if (m_emptyline_choice->isChecked()) return "\n\n"; else if (m_newline_choice->isChecked()) return "\n"; else if (m_dash_choice->isChecked()) return "\n-"; else if (m_star_choice->isChecked()) return "\n*"; else if (m_anotherSeparator->isChecked()) return m_customSeparator->toPlainText(); else if (m_all_in_one_choice->isChecked()) return ""; else return "\n\n"; } void TextFileImportDialog::customSeparatorChanged() { if (!m_anotherSeparator->isChecked()) m_anotherSeparator->toggle(); } /** namespace SoftwareImporters: */ QString SoftwareImporters::fromICS(const QString &ics) { QString result = ics; // Remove escaped '\' characters and append the text to the body int pos = 0; while ((pos = result.indexOf('\\', pos)) != -1) { if (pos == result.length() - 1) // End of string break; if (result[pos + 1] == 'n') { result.replace(pos, 2, '\n'); } else if (result[pos + 1] == 'r') { result.replace(pos, 2, '\r'); } else if (result[pos + 1] == 't') { result.replace(pos, 2, '\t'); } else if (result[pos] == '\\') { result.remove(pos, 1); // Take care of "\\", "\,", "\;" and other escaped characters I haven't noticed ++pos; } } return result; } QString SoftwareImporters::fromTomboy(QString tomboy) { // The first line is the note title, and we already have it, so we remove it (yes, that's pretty stupid to duplicate it in the content...): tomboy = tomboy.mid(tomboy.indexOf("\n")).trimmed(); // Font styles and decorations: tomboy.replace("", ""); tomboy.replace("", ""); tomboy.replace("", ""); tomboy.replace("", ""); tomboy.replace("", ""); tomboy.replace("", ""); // Highlight not supported by KTextEdit: // TODO: implement this marker-style yellow highlight? tomboy.replace("", ""); tomboy.replace("", ""); // Font sizes: tomboy.replace("", ""); tomboy.replace("", ""); tomboy.replace("", ""); tomboy.replace("", ""); tomboy.replace("", ""); tomboy.replace("", ""); // Internal links to other notes aren't supported yet by BasKet Note Pads: tomboy.replace("", ""); tomboy.replace("", ""); // Bullets tomboy.replace("", "
      "); tomboy.replace("", "
    "); QRegExp tagRegex(""); tagRegex.setMinimal(true); // non-greedy tomboy.replace(QRegExp(tagRegex), "
  • "); tomboy.replace("", "
  • "); // In the Tomboy file, new lines are "\n" and not "
    ": tomboy.replace('\n', "
    \n"); // Preserve consecutive spaces: return "" + tomboy + ""; } QString SoftwareImporters::getFirstTomboyTag(const QDomElement &docElem) { // Would be better to take the last tag (latest user choice), but that requires more DOM code QString tag = XMLWork::getElementText(docElem, "tags/tag"); if (tag.length() > 0) { // Carefully peel the markup const QString SYSTEM_PREFIX = "system:"; const QString NOTEBOOK_PREFIX = "notebook:"; if (tag.startsWith(SYSTEM_PREFIX)) tag.remove(0, SYSTEM_PREFIX.length()); if (tag.startsWith(NOTEBOOK_PREFIX)) tag.remove(0, NOTEBOOK_PREFIX.length()); } return tag; } Note *SoftwareImporters::insertTitledNote(BasketScene *parent, const QString &title, const QString &content, Qt::TextFormat format /* = Qt::PlainText*/, Note *parentNote /* = 0*/) { Note *nGroup = new Note(parent); Note *nTitle = NoteFactory::createNoteText(title, parent); nTitle->addState(Tag::stateForId("title")); Note *nContent; if (format == Qt::PlainText) nContent = NoteFactory::createNoteText(content, parent); else nContent = NoteFactory::createNoteHtml(content, parent); if (parentNote == 0) parentNote = parent->firstNote(); // In the first column! parent->insertNote(nGroup, parentNote, Note::BottomColumn, QPoint(), /*animate=*/false); parent->insertNote(nTitle, nGroup, Note::BottomColumn, QPoint(), /*animate=*/false); parent->insertNote(nContent, nTitle, Note::BottomInsert, QPoint(), /*animate=*/false); return nGroup; } void SoftwareImporters::finishImport(BasketScene *basket) { // Unselect the last inserted group: basket->unselectAll(); // Focus the FIRST note (the last inserted note is currently focused!): basket->setFocusedNote(basket->firstNoteShownInStack()); // Relayout every notes at their new place and simulate a load animation (because already loaded just after the creation). // Without a relayouting, notes on the bottom would comes from the top (because they were inserted on top) and clutter the animation load: basket->relayoutNotes(/*animate=*/false); basket->animateLoad(); basket->save(); } void SoftwareImporters::importKJots() { // This code is out-of-date. KJots (KDE4) and KNotes (KDE4, 5) now store data in Akonadi and MIME files in .local/share/notes QString dirPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/../kjots/"; // I think the assumption is good QDir dir(dirPath, QString(), QDir::Name | QDir::IgnoreCase, QDir::Files | QDir::NoSymLinks); QStringList list = dir.entryList(); if (list.isEmpty()) return; BasketFactory::newBasket(/*icon=*/"kjots", /*name=*/i18n("From KJots"), /*backgroundImage=*/"", /*backgroundColor=*/QColor(), /*textColor=*/QColor(), /*templateName=*/"1column", /*createIn=*/0); BasketScene *kjotsBasket = Global::bnpView->currentBasket(); for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) { // For each file QFile file(dirPath + *it); if (file.open(QIODevice::ReadOnly)) { QTextStream stream(&file); QString buf = stream.readLine(); // IT IS A NOTEBOOK FILE, AT THE VERION 0.6.x and older: if (!buf.isNull() && buf.left(9) == "\\NewEntry") { // First create a basket for it: BasketFactory::newBasket( /*icon=*/"kjots", /*name=*/QUrl::fromLocalFile(file.fileName()).fileName(), /*backgroundImage=*/"", /*backgroundColor=*/QColor(), /*textColor=*/QColor(), /*templateName=*/"1column", /*createIn=*/kjotsBasket); BasketScene *basket = Global::bnpView->currentBasket(); basket->load(); QString title, body; bool haveAnEntry = false; while (1) { if (buf.left(9) == "\\NewEntry") { if (haveAnEntry) // Do not add note the first time insertTitledNote(basket, title, Tools::stripEndWhiteSpaces(body)); title = buf.mid(10, buf.length()); // Problem : basket will be saved body = ""; // New note will then be created // EACH time a note is imported haveAnEntry = true; } else if (buf.left(3) != "\\ID") { // Don't care of the ID // Remove escaped '\' characters and append the text to the body int pos = 0; while ((pos = buf.indexOf('\\', pos)) != -1) if (buf[++pos] == '\\') buf.remove(pos, 1); body.append(buf + '\n'); } buf = stream.readLine(); if (buf.isNull()) // OEF break; } // Add the ending note (there isn't any other "\\NewEntry" to do it): if (haveAnEntry) insertTitledNote(basket, title, Tools::stripEndWhiteSpaces(body)); finishImport(basket); // IT IS A NOTEBOOK XML FILE, AT THE VERION 0.7.0 and later: } else if ((*it).endsWith(QLatin1String(".book")) /*&& !buf.isNull() && (buf.left(2) == "documentElement(), "KJotsBook/Title"); // First create a basket for it: BasketFactory::newBasket(/*icon=*/"kjots", /*name=*/bookTitle, /*backgroundImage=*/"", /*backgroundColor=*/QColor(), /*textColor=*/QColor(), /*templateName=*/"1column", /*createIn=*/kjotsBasket); BasketScene *basket = Global::bnpView->currentBasket(); basket->load(); QDomElement docElem = XMLWork::getElement(doc->documentElement(), "KJotsBook"); for (QDomNode n = docElem.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement e = n.toElement(); if ((!e.isNull()) && e.tagName() == "KJotsPage") insertTitledNote(basket, XMLWork::getElementText(e, "Title"), XMLWork::getElementText(e, "Text")); } finishImport(basket); } file.close(); } } } void SoftwareImporters::importKNotes() { QString dirPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/../knotes/"; // I think the assumption is good QDir dir(dirPath, QString(), QDir::Name | QDir::IgnoreCase, QDir::Files | QDir::NoSymLinks); QStringList list = dir.entryList(); for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) { // For each file if (!(*it).endsWith(QLatin1String(".ics"))) // Don't process *.ics~ and other files continue; QFile file(dirPath + *it); if (file.open(QIODevice::ReadOnly)) { QTextStream stream(&file); stream.setCodec("UTF-8"); // First create a basket for it: BasketFactory::newBasket(/*icon=*/"knotes", /*name=*/i18n("From KNotes"), /*backgroundImage=*/"", /*backgroundColor=*/QColor(), /*textColor=*/QColor(), /*templateName=*/"1column", /*createIn=*/0); BasketScene *basket = Global::bnpView->currentBasket(); basket->load(); bool inVJournal = false; bool inDescription = false; bool isRichText = false; QString title, body; QString buf; while (1) { buf = stream.readLine(); if (buf.isNull()) // OEF break; if (!buf.isNull() && buf == "BEGIN:VJOURNAL") { inVJournal = true; } else if (inVJournal && buf.startsWith(QLatin1String("SUMMARY:"))) { title = buf.mid(8, buf.length()); } else if (inVJournal && buf.startsWith(QLatin1String("DESCRIPTION:"))) { body = buf.mid(12, buf.length()); inDescription = true; } else if (inDescription && buf.startsWith(QLatin1String(" "))) { body += buf.mid(1, buf.length()); } else if (buf.startsWith(QLatin1String("X-KDE-KNotes-RichText:"))) { isRichText = XMLWork::trueOrFalse(buf.mid(22, buf.length() - 22).trimmed(), "false"); } else if (buf == "END:VJOURNAL") { insertTitledNote(basket, fromICS(title), fromICS(body), (isRichText ? Qt::RichText : Qt::PlainText)); inVJournal = false; inDescription = false; isRichText = false; title = ""; body = ""; } else inDescription = false; } // Bouh : duplicate code // In case of invalid ICAL file! if (!body.isEmpty()) // Add the ending note insertTitledNote(basket, fromICS(title), fromICS(body), (isRichText ? Qt::RichText : Qt::PlainText)); file.close(); finishImport(basket); } } } void SoftwareImporters::importStickyNotes() { // Sticky Notes file is usually located in ~/.gnome2/stickynotes_applet // We will search all directories in "~/" that contain "gnome" in the name, // and will search for "stickynotes_applet" file (that should be XML file with root. QDir dir(QDir::home().absolutePath(), QString(), QDir::Name | QDir::IgnoreCase, QDir::Dirs | QDir::NoSymLinks | QDir::Hidden); QStringList founds; QStringList list = dir.entryList(); for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) { // For each folder if ((*it).contains("gnome", Qt::CaseInsensitive)) { QString fullPath = QDir::home().absolutePath() + '/' + (*it) + "/stickynotes_applet"; if (dir.exists(fullPath)) founds += fullPath; } } for (QStringList::Iterator it = founds.begin(); it != founds.end(); ++it) { // For each file QFile file(*it); QDomDocument *doc = XMLWork::openFile("stickynotes", *it); if (doc == 0) continue; // First create a basket for it: BasketFactory::newBasket(/*icon=*/"gnome", /*name=*/i18n("From Sticky Notes"), /*backgroundImage=*/"", /*backgroundColor=*/QColor(), /*textColor=*/QColor(), /*templateName=*/"1column", /*createIn=*/0); BasketScene *basket = Global::bnpView->currentBasket(); basket->load(); QDomElement docElem = doc->documentElement(); for (QDomNode n = docElem.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement e = n.toElement(); if ((!e.isNull()) && e.tagName() == "note") insertTitledNote(basket, e.attribute("title"), e.text()); } finishImport(basket); } } // TODO: FIXME: Code duplicated from notecontent.cpp but with UTF-8 encoding. // TODO: FIXME: Later, merge! QString loadUtf8FileToString(const QString &fileName) { QFile file(fileName); if (file.open(QIODevice::ReadOnly)) { QTextStream stream(&file); stream.setCodec("UTF-8"); QString text; text = stream.readAll(); file.close(); return text; } else return ""; } void SoftwareImporters::importTomboy() { /* A notebook becomes a basket. What can be improved: separate big text into notes by "\n\n"; remove "\n" from lists - we don't need
    between two
  • s */ BasketScene *basketFromTomboy = 0; // Create the basket ONLY if we found at least one note to add! BasketScene *subBasket; // Where are we adding current note? (can equal basketFromTomboy if the note is in generic notebook) QMap subBuskets; // "notebook name - basket" correspondence QString possibleLocations[2] = {"/.tomboy/", "/.local/share/tomboy/"}; for (int L = 0; L < 2; L++) { QString dirPath = QDir::home().absolutePath() + possibleLocations[L]; QDir dir(dirPath, QString(), QDir::Name | QDir::IgnoreCase, QDir::Files | QDir::NoSymLinks); DEBUG_WIN << "Tomboy import: Checking " + dirPath; QStringList list = dir.entryList(); for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) { // For each file if (!(*it).endsWith(QLatin1String(".note"))) continue; QDomDocument *doc = XMLWork::openFile("note", dirPath + *it); if (doc == 0) continue; if (basketFromTomboy == 0) { // First create a basket for it: BasketFactory::newBasket(/*icon=*/IconNames::TOMBOY, /*name=*/i18n("From Tomboy"), /*backgroundImage=*/"", /*backgroundColor=*/QColor(), /*textColor=*/QColor(), /*templateName=*/"1column", /*createIn=*/0); basketFromTomboy = Global::bnpView->currentBasket(); basketFromTomboy->load(); } QDomElement docElem = doc->documentElement(); QString title = XMLWork::getElementText(docElem, "title"); QString notebook = getFirstTomboyTag(docElem); //!< Tomboy notebook name // Find subbasket or create a new one if (notebook.length() > 0) { if (subBuskets.contains(notebook)) subBasket = subBuskets[notebook]; else { BasketFactory::newBasket(IconNames::TOMBOY, notebook, "", QColor(), QColor(), "1column", /*createIn=*/basketFromTomboy); subBasket = Global::bnpView->currentBasket(); // TODO: ? change newBasket() so that it returns pointer and we don't have to load it subBasket->load(); subBuskets.insert(notebook, subBasket); } } else subBasket = basketFromTomboy; // will insert to the root basket // DOES NOT REALLY WORKS: // QDomElement contentElement = XMLWork::getElement(docElem, "text/note-content"); // QString content = XMLWork::innerXml(contentElement); // Isolate "CONTENT"! QString xml = loadUtf8FileToString(dirPath + *it); xml = xml.mid(xml.indexOf("") + 1); xml = xml.mid(0, xml.indexOf("")); if (!title.isEmpty() && !/*content*/ xml.isEmpty()) { insertTitledNote(subBasket, title, fromTomboy(xml /*content*/), Qt::RichText); DEBUG_WIN << QString("Tomboy import: Inserting note '%1' into '%2'").arg(title, notebook); } } } if (basketFromTomboy) finishImport(basketFromTomboy); } void SoftwareImporters::importJreepadFile() { typedef QPair basketAndElementPair; QString fileName = QFileDialog::getOpenFileName(0, QString(), "kfiledialog:///:ImportJreepadFile", "*.xml|XML files"); if (fileName.isEmpty()) { return; } basketAndElementPair newElement; basketAndElementPair currentElement; QList elements; QList basketList; QDomDocument *doc = XMLWork::openFile("node", fileName); newElement.second = doc->documentElement(); BasketScene *basket = 0; BasketFactory::newBasket(/*icon=*/"xml", /*name=*/doc->documentElement().attribute("title"), /*backgroundImage=*/"", /*backgroundColor=*/QColor(), /*textColor=*/QColor(), /*templateName=*/"1column", /*createIn=*/0); basket = Global::bnpView->currentBasket(); basket->load(); basketList << basket; newElement.first = basket; elements << newElement; while (!elements.isEmpty()) { currentElement = elements.takeFirst(); for (QDomNode n = currentElement.second.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.isText()) { basket = currentElement.first; Note *note = NoteFactory::createNoteFromText(n.toText().data(), basket); basket->insertNote(note, basket->firstNote(), Note::BottomColumn, QPoint(), /*animate=*/false); } else if (n.isElement()) { BasketFactory::newBasket(/*icon=*/"xml", /*name=*/n.toElement().attribute("title"), /*backgroundImage=*/"", /*backgroundColor=*/QColor(), /*textColor=*/QColor(), /*templateName=*/"1column", /*createIn=*/currentElement.first); basket = Global::bnpView->currentBasket(); basket->load(); basketList << basket; newElement.first = basket; newElement.second = n.toElement(); elements << newElement; } } } foreach (basket, basketList) { finishImport(basket); } } void SoftwareImporters::importTextFile() { QString fileName = QFileDialog::getOpenFileName(0, QString(), "kfiledialog:///:ImportTextFile", "*|All files"); if (fileName.isEmpty()) return; TextFileImportDialog dialog; if (dialog.exec() == QDialog::Rejected) return; QString separator = dialog.separator(); QFile file(fileName); if (file.open(QIODevice::ReadOnly)) { QTextStream stream(&file); QString content = stream.readAll(); QStringList list = (separator.isEmpty() ? QStringList(content) : content.split(separator)); // First create a basket for it: QString title = i18nc("From TextFile.txt", "From %1", QUrl::fromLocalFile(fileName).fileName()); BasketFactory::newBasket(/*icon=*/"txt", title, /*backgroundImage=*/"", /*backgroundColor=*/QColor(), /*textColor=*/QColor(), /*templateName=*/"1column", /*createIn=*/0); BasketScene *basket = Global::bnpView->currentBasket(); basket->load(); // Import every notes: for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) { Note *note = NoteFactory::createNoteFromText((*it).trimmed(), basket); basket->insertNote(note, basket->firstNote(), Note::BottomColumn, QPoint(), /*animate=*/false); } // Finish the export: finishImport(basket); } } /** @author Petri Damsten */ void SoftwareImporters::importKnowIt() { QUrl url = QFileDialog::getOpenFileUrl(NULL, "", QUrl("kfiledialog:///:ImportKnowIt"), "*.kno|KnowIt files\n*|All files"); if (!url.isEmpty()) { QFile file(url.path()); QFileInfo info(url.path()); BasketScene *basket = 0; QStack baskets; QString text; int hierarchy = 0; TreeImportDialog dialog; if (dialog.exec() == QDialog::Rejected) return; hierarchy = dialog.choice(); BasketFactory::newBasket(/*icon=*/"knowit", /*name=*/info.baseName(), /*backgroundImage=*/"", /*backgroundColor=*/QColor(), /*textColor=*/QColor(), /*templateName=*/"1column", /*createIn=*/0); basket = Global::bnpView->currentBasket(); basket->load(); baskets.push(basket); if (file.open(QIODevice::ReadOnly)) { QTextStream stream(&file); int level = 0; QString name; QString line; QStringList links; QStringList descriptions; stream.setCodec("UTF-8"); while (1) { line = stream.readLine(); if (line.startsWith(QLatin1String("\\NewEntry")) || line.startsWith(QLatin1String("\\CurrentEntry")) || stream.atEnd()) { while (level + 1 < baskets.size() - baskets.count(0)) baskets.pop(); if (level + 1 > baskets.size() - baskets.count(0)) baskets.push(basket); if (!name.isEmpty()) { if ((level == 0 && hierarchy == 1) || (hierarchy == 0)) { BasketFactory::newBasket(/*icon=*/"knowit", /*name=*/name, /*backgroundImage=*/"", /*backgroundColor=*/QColor(), /*textColor=*/QColor(), /*templateName=*/"1column", /*createIn=*/baskets.top()); basket = Global::bnpView->currentBasket(); basket->load(); } if (!text.trimmed().isEmpty() || hierarchy == 2 || (hierarchy == 1 && level > 0)) { insertTitledNote(basket, name, text, Qt::RichText); } for (int j = 0; j < links.count(); ++j) { Note *link; if (descriptions.count() < j + 1 || descriptions[j].isEmpty()) link = NoteFactory::createNoteLink(links[j], basket); else link = NoteFactory::createNoteLink(links[j], descriptions[j], basket); basket->insertCreatedNote(link); } finishImport(basket); } if (stream.atEnd()) break; int i = line.indexOf("Entry") + 6; int n = line.indexOf(' ', i); level = line.mid(i, n - i).toInt(); name = line.mid(n + 1); text = ""; links.clear(); descriptions.clear(); } else if (line.startsWith(QLatin1String("\\Link"))) { links.append(line.mid(6)); } else if (line.startsWith(QLatin1String("\\Descr"))) { while (descriptions.count() < links.count() - 1) descriptions.append(""); descriptions.append(line.mid(7)); } else { text += line + '\n'; } } file.close(); } } } void SoftwareImporters::importTuxCards() { QString fileName = QFileDialog::getOpenFileName(0, QString(), "kfiledialog:///:ImportTuxCards", "*|All files"); if (fileName.isEmpty()) return; TreeImportDialog dialog; if (dialog.exec() == QDialog::Rejected) return; int hierarchy = dialog.choice(); QDomDocument *document = XMLWork::openFile("tuxcards_data_file" /*"InformationCollection"*/, fileName); if (document == 0) { KMessageBox::error(0, i18n("Can not import that file. It is either corrupted or not a TuxCards file."), i18n("Bad File Format")); return; } QDomElement collection = document->documentElement(); int remainingHierarchy = (hierarchy == 0 ? 65000 : 3 - hierarchy); importTuxCardsNode(collection, /*parentBasket=*/0, /*parentNote=*/0, remainingHierarchy); } // TODO: Encrypted note.
    The importer do not support encrypted notes yet. Please remove the encryption with TuxCards and re-import the file."); } if (remainingHierarchy > 0) { BasketFactory::newBasket(icon, name, /*backgroundImage=*/"", /*backgroundColor=*/QColor(), /*textColor=*/QColor(), /*templateName=*/"1column", parentBasket); BasketScene *basket = Global::bnpView->currentBasket(); basket->load(); if (isRichText) nContent = NoteFactory::createNoteHtml(content, basket); else nContent = NoteFactory::createNoteText(content, basket); basket->insertNote(nContent, basket->firstNote(), Note::BottomColumn, QPoint(), /*animate=*/false); importTuxCardsNode(e, basket, 0, remainingHierarchy - 1); finishImport(basket); } else { Note *nGroup = insertTitledNote(parentBasket, name, content, (isRichText ? Qt::RichText : Qt::PlainText), parentNote); importTuxCardsNode(e, parentBasket, nGroup, remainingHierarchy - 1); } } } diff --git a/src/softwareimporters.h b/src/softwareimporters.h index 591e9da..000df73 100644 --- a/src/softwareimporters.h +++ b/src/softwareimporters.h @@ -1,106 +1,93 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef SOFTWAREIMPORTERS_H #define SOFTWAREIMPORTERS_H #include class QString; class QGroupBox; class QDomElement; class QRadioButton; class KTextEdit; class QVBoxLayout; class BasketScene; class Note; /** The dialog to ask how to import hierarchical data. * @author Sébastien Laoût */ class TreeImportDialog : public QDialog { Q_OBJECT public: explicit TreeImportDialog(QWidget *parent = nullptr); ~TreeImportDialog() override; int choice(); private: QGroupBox *m_choices; QVBoxLayout *m_choiceLayout; QRadioButton *m_hierarchy_choice; QRadioButton *m_separate_baskets_choice; QRadioButton *m_one_basket_choice; }; /** The dialog to ask how to import text files. * @author Sébastien Laoût */ class TextFileImportDialog : public QDialog { Q_OBJECT public: explicit TextFileImportDialog(QWidget *parent = nullptr); ~TextFileImportDialog() override; QString separator(); protected slots: void customSeparatorChanged(); private: QGroupBox *m_choices; QVBoxLayout *m_choiceLayout; QRadioButton *m_emptyline_choice; QRadioButton *m_newline_choice; QRadioButton *m_dash_choice; QRadioButton *m_star_choice; QRadioButton *m_all_in_one_choice; QRadioButton *m_anotherSeparator; KTextEdit *m_customSeparator; }; /** Functions that import data from other softwares. * @author Sébastien Laoût */ namespace SoftwareImporters { // Useful methods to design importers: QString fromICS(const QString &ics); QString fromTomboy(QString tomboy); //! Get first of the to be used as basket name. Strip 'system:notebook:' part QString getFirstTomboyTag(const QDomElement &docElem); Note *insertTitledNote(BasketScene *parent, const QString &title, const QString &content, Qt::TextFormat format = Qt::PlainText, Note *parentNote = 0); void finishImport(BasketScene *basket); // The importers in themselves: void importKNotes(); void importKJots(); void importKnowIt(); void importTuxCards(); void importStickyNotes(); void importTomboy(); void importJreepadFile(); void importTextFile(); // void importTuxCardsNode(const QDomElement &element, BasketScene *parentBasket, Note *parentNote, int remainingHierarchy); } #endif // SOFTWAREIMPORTERS_H diff --git a/src/systemtray.cpp b/src/systemtray.cpp index 1bbeeda..e90b5c0 100644 --- a/src/systemtray.cpp +++ b/src/systemtray.cpp @@ -1,357 +1,344 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ /** SystemTray */ #include "systemtray.h" // Qt #include #include #include #include // Frameworks #include //Port to Blitz/Quasar? #include // Local #include "basketscene.h" #include "global.h" #include "icon_names.h" #include "settings.h" #include "tools.h" /* This function comes directly from JuK: */ /* * This function copies the entirety of src into dest, starting in * dest at x and y. This function exists because I was unable to find * a function like it in either QImage or kdefx */ static bool copyImage(QImage &dest, QImage &src, int x, int y) { if (dest.depth() != src.depth()) return false; if ((x + src.width()) >= dest.width()) return false; if ((y + src.height()) >= dest.height()) return false; // We want to use KIconEffect::overlay to do this, since it handles // alpha, but the images need to be the same size. We can handle that. QImage large_src(dest); // It would perhaps be better to create large_src based on a size, but // this is the easiest way to make a new image with the same depth, size, // etc. large_src.detach(); // However, we do have to specifically ensure that setAlphaBuffer is set // to false // large_src.setAlphaBuffer(false); large_src.fill(0); // All transparent pixels // large_src.setAlphaBuffer(true); int w = src.width(); int h = src.height(); for (int dx = 0; dx < w; dx++) for (int dy = 0; dy < h; dy++) large_src.setPixel(dx + x, dy + y, src.pixel(dx, dy)); // Apply effect to image KIconEffect::overlay(dest, large_src); return true; } /** Constructor */ SystemTray::SystemTray(QWidget *parent) : KStatusNotifierItem(parent) { updateDisplay(); } /** Destructor */ SystemTray::~SystemTray() { // pass } /** Updates the icon and tooltip in the system tray */ void SystemTray::updateDisplay() { BasketScene *basket = Global::bnpView->currentBasket(); if (!basket) return; // Update the icon if (basket->icon().isEmpty() || basket->icon() == "basket" || !Settings::showIconInSystray()) setIconByName("basket"); else { // What pixmap size to use? For example, see how overlay icon is applied: // https://api.kde.org/frameworks-api/frameworks5-apidocs/knotifications/html/kstatusnotifieritem_8cpp_source.html // Code that comes from JuK: QPixmap bgPix = QIcon::fromTheme("basket").pixmap(22); QPixmap fgPix = QIcon::fromTheme(basket->icon()).pixmap(16); QImage bgImage = bgPix.toImage(); // Probably 22x22 QImage fgImage = fgPix.toImage(); // Should be 16x16 KIconEffect::semiTransparent(bgImage); copyImage(bgImage, fgImage, (bgImage.width() - fgImage.width()) / 2, (bgImage.height() - fgImage.height()) / 2); setIconByPixmap(QPixmap::fromImage(bgImage)); } setOverlayIconByName(basket->isLocked() ? IconNames::LOCKED : ""); // update the tooltip QString tip = "

    "; QString basketName = "%1"; if (basket->isLocked()) basketName += i18n(" (Locked)"); tip += Tools::makeStandardCaption(basketName); tip = tip.arg(Tools::textToHTMLWithoutP(basket->basketName())); setToolTipTitle(tip); } #ifdef USE_OLD_SYSTRAY #define QT3_SUPPORT // No need to port that old stuff SystemTray::SystemTray(QWidget *parent, const char *name) : KSystemTray2(parent, name != 0 ? name : "SystemTray") , m_showTimer(0) , m_autoShowTimer(0) { setAcceptDrops(true); m_showTimer = new QTimer(this); connect(m_showTimer, SIGNAL(timeout()), Global::bnpView, SLOT(setActive())); m_autoShowTimer = new QTimer(this); connect(m_autoShowTimer, SIGNAL(timeout()), Global::bnpView, SLOT(setActive())); // Create pixmaps for the icon: m_iconPixmap = loadIcon("basket"); // FIXME: When main window is shown at start, the icon is loaded 1 pixel too high // and then reloaded instantly after at the right position. // setPixmap(m_iconPixmap); // Load it the sooner as possible to avoid flicker QImage lockedIconImage = m_iconPixmap.convertToImage(); QPixmap lockOverlayPixmap = loadIcon("object-locked"); QImage lockOverlayImage = lockOverlayPixmap.convertToImage(); KIconEffect::overlay(lockedIconImage, lockOverlayImage); m_lockedIconPixmap.convertFromImage(lockedIconImage); updateToolTip(); // Set toolTip AND icon } SystemTray::~SystemTray() { } void SystemTray::mousePressEvent(QMouseEvent *event) { if (event->button() & Qt::LeftButton) { // Prepare drag m_pressPos = event->globalPos(); m_canDrag = true; event->accept(); } else if (event->button() & Qt::MidButton) { // Paste Global::bnpView->currentBasket()->setInsertPopupMenu(); Global::bnpView->currentBasket()->pasteNote(QClipboard::Selection); Global::bnpView->currentBasket()->cancelInsertPopupMenu(); if (Settings::usePassivePopup()) Global::bnpView->showPassiveDropped(i18n("Pasted selection to basket %1")); event->accept(); } else if (event->button() & Qt::RightButton) { // Popup menu QMenu menu(this); menu.addSection(SmallIcon("basket"), QGuiApplication::applicationDisplayName()); Global::bnpView->actNewBasket->plug(&menu); Global::bnpView->actNewSubBasket->plug(&menu); Global::bnpView->actNewSiblingBasket->plug(&menu); menu.insertSeparator(); Global::bnpView->m_actPaste->plug(&menu); Global::bnpView->m_actGrabScreenshot->plug(&menu); Global::bnpView->m_actColorPicker->plug(&menu); if (!Global::bnpView->isPart()) { QAction *action; menu.insertSeparator(); action = Global::bnpView->actionCollection()->action("options_configure_global_keybinding"); if (action) action->plug(&menu); action = Global::bnpView->actionCollection()->action("options_configure"); if (action) action->plug(&menu); menu.insertSeparator(); // Minimize / restore : since we manage the popup menu by ourself, we should do that work : action = Global::bnpView->actionCollection()->action("minimizeRestore"); if (action) { if (Global::mainWindow()->isVisible()) action->setText(i18n("&Minimize")); else action->setText(i18n("&Restore")); action->plug(&menu); } action = Global::bnpView->actionCollection()->action("file_quit"); if (action) action->plug(&menu); } Global::bnpView->currentBasket()->setInsertPopupMenu(); connect(&menu, SIGNAL(aboutToHide()), Global::bnpView->currentBasket(), SLOT(delayedCancelInsertPopupMenu())); menu.exec(event->globalPos()); event->accept(); } else event->ignore(); } void SystemTray::mouseMoveEvent(QMouseEvent *event) { event->ignore(); } void SystemTray::mouseReleaseEvent(QMouseEvent *event) { m_canDrag = false; if (event->button() == Qt::LeftButton) // Show / hide main window if (rect().contains(event->pos())) { // Accept only if released in systemTray toggleActive(); emit showPart(); event->accept(); } else event->ignore(); } void SystemTray::dragEnterEvent(QDragEnterEvent *event) { m_showTimer->start(Settings::dropTimeToShow() * 100, true); Global::bnpView->currentBasket()->showFrameInsertTo(); /// m_parentContainer->setStatusBarDrag(); // FIXME: move this line in BasketScene::showFrameInsertTo() ? BasketScene::acceptDropEvent(event); } void SystemTray::dragMoveEvent(QDragMoveEvent *event) { BasketScene::acceptDropEvent(event); } void SystemTray::dragLeaveEvent(QDragLeaveEvent *) { m_showTimer->stop(); m_canDrag = false; Global::bnpView->currentBasket()->resetInsertTo(); Global::bnpView->updateStatusBarHint(); } #include void SystemTray::dropEvent(QDropEvent *event) { m_showTimer->stop(); Global::bnpView->currentBasket()->blindDrop(event); /* BasketScene *basket = Global::bnpView->currentBasket(); if (!basket->isLoaded()) { Global::bnpView->showPassiveLoading(basket); basket->load(); } basket->contentsDropEvent(event); qDebug() << (long) basket->selectedNotes(); if (Settings::usePassivePopup()) Global::bnpView->showPassiveDropped(i18n("Dropped to basket %1", m_basketName));*/ } void SystemTray::updateToolTip() { // return; ///////////////////////////////////////////////////// BasketScene *basket = Global::bnpView->currentBasket(); if (!basket) return; if (basket->icon().isEmpty() || basket->icon() == "basket" || !Settings::showIconInSystray()) setPixmap(basket->isLocked() ? m_lockedIconPixmap : m_iconPixmap); else { // Code that comes from JuK: QPixmap bgPix = loadIcon("basket"); QPixmap fgPix = SmallIcon(basket->icon()); QImage bgImage = bgPix.convertToImage(); // Probably 22x22 QImage fgImage = fgPix.convertToImage(); // Should be 16x16 QImage lockOverlayImage = loadIcon("object-locked").convertToImage(); KIconEffect::semiTransparent(bgImage); copyImage(bgImage, fgImage, (bgImage.width() - fgImage.width()) / 2, (bgImage.height() - fgImage.height()) / 2); if (basket->isLocked()) KIconEffect::overlay(bgImage, lockOverlayImage); bgPix.convertFromImage(bgImage); setPixmap(bgPix); } // QTimer::singleShot( Container::c_delayTooltipTime, this, SLOT(updateToolTipDelayed()) ); // No need to delay: it's be called when notes are changed: updateToolTipDelayed(); } void SystemTray::updateToolTipDelayed() { BasketScene *basket = Global::bnpView->currentBasket(); QString tip = "

    " + (basket->isLocked() ? Tools::makeStandardCaption(i18n("%1 (Locked)")) : Tools::makeStandardCaption("%1")).arg(Tools::textToHTMLWithoutP(basket->basketName())); QToolTip::add(this, tip); } void SystemTray::wheelEvent(QWheelEvent *event) { if (event->delta() > 0) Global::bnpView->goToPreviousBasket(); else Global::bnpView->goToNextBasket(); if (Settings::usePassivePopup()) Global::bnpView->showPassiveContent(); } void SystemTray::enterEvent(QEvent *) { if (Settings::showOnMouseIn()) m_autoShowTimer->start(Settings::timeToShowOnMouseIn() * 100, true); } void SystemTray::leaveEvent(QEvent *) { m_autoShowTimer->stop(); } #undef QT3_SUPPORT #endif // USE_OLD_SYSTRAY diff --git a/src/systemtray.h b/src/systemtray.h index 08baa7b..2b84df8 100644 --- a/src/systemtray.h +++ b/src/systemtray.h @@ -1,79 +1,66 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef SYSTEMTRAY_H #define SYSTEMTRAY_H #include #include #include /** A thin wrapper around KSystemTrayIcon until the old SystemTray is ported. * As things are ported, items should * @author Kelvie Wong */ class SystemTray : public KStatusNotifierItem { Q_OBJECT Q_DISABLE_COPY(SystemTray); public: explicit SystemTray(QWidget *parent = nullptr); ~SystemTray() override; public slots: void updateDisplay(); signals: void showPart(); }; #ifdef USE_OLD_SYSTRAY /** This class provide a personalized system tray icon. * @author Sébastien Laoût */ class SystemTray2 : public SystemTray { Q_OBJECT public: explicit SystemTray2(QWidget *parent = nullptr, const char *name = nullptr); ~SystemTray2(); protected: void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); virtual void dragEnterEvent(QDragEnterEvent *event); virtual void dragMoveEvent(QDragMoveEvent *event); virtual void dragLeaveEvent(QDragLeaveEvent *); virtual void dropEvent(QDropEvent *event); void wheelEvent(QWheelEvent *event); void enterEvent(QEvent *); void leaveEvent(QEvent *); private: QTimer *m_showTimer; QTimer *m_autoShowTimer; bool m_canDrag; QPoint m_pressPos; }; #endif // USE_OLD_SYSTRAY #endif // SYSTEMTRAY_H diff --git a/src/tag.cpp b/src/tag.cpp index 94dd4ee..6a744a3 100644 --- a/src/tag.cpp +++ b/src/tag.cpp @@ -1,776 +1,763 @@ -/*************************************************************************** - * Copyright (C) 2005 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2005 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "tag.h" #include #include #include #include #include #include #include #include #include #include "basketscene.h" #include "bnpview.h" #include "debugwindow.h" #include "gitwrapper.h" #include "global.h" #include "tools.h" #include "xmlwork.h" /** class State: */ State::State(const QString &id, Tag *tag) : m_id(id) , m_name() , m_emblem() , m_bold(false) , m_italic(false) , m_underline(false) , m_strikeOut(false) , m_textColor() , m_fontName() , m_fontSize(-1) , m_backgroundColor() , m_textEquivalent() , m_onAllTextLines(false) , m_allowCrossReferences(true) , m_parentTag(tag) { } State::~State() { } State *State::nextState(bool cycle /*= true*/) { if (!parentTag()) return 0; List states = parentTag()->states(); // The tag contains only one state: if (states.count() == 1) return 0; // Find the next state: for (List::iterator it = states.begin(); it != states.end(); ++it) // Found the current state in the list: if (*it == this) { // Find the next state: State *next = *(++it); if (it == states.end()) return (cycle ? states.first() : 0); return next; } // Should not happens: Q_ASSERT(false); return 0; } QString State::fullName() { if (!parentTag() || parentTag()->states().count() == 1) return (name().isEmpty() && parentTag() ? parentTag()->name() : name()); return QString(i18n("%1: %2", parentTag()->name(), name())); } QFont State::font(QFont base) { if (bold()) base.setBold(true); if (italic()) base.setItalic(true); if (underline()) base.setUnderline(true); if (strikeOut()) base.setStrikeOut(true); if (!fontName().isEmpty()) base.setFamily(fontName()); if (fontSize() > 0) base.setPointSize(fontSize()); return base; } QString State::toCSS(const QString &gradientFolderPath, const QString &gradientFolderName, const QFont &baseFont) { QString css; if (bold()) css += " font-weight: bold;"; if (italic()) css += " font-style: italic;"; if (underline() && strikeOut()) css += " text-decoration: underline line-through;"; else if (underline()) css += " text-decoration: underline;"; else if (strikeOut()) css += " text-decoration: line-through;"; if (textColor().isValid()) css += " color: " + textColor().name() + ';'; if (!fontName().isEmpty()) { QString fontFamily = Tools::cssFontDefinition(fontName(), /*onlyFontFamily=*/true); css += " font-family: " + fontFamily + ';'; } if (fontSize() > 0) css += " font-size: " + QString::number(fontSize()) + "px;"; if (backgroundColor().isValid()) { css += " background-color: " + backgroundColor().name() + ";"; } if (css.isEmpty()) return ""; else return " .tag_" + id() + " {" + css + " }\n"; } void State::merge(const List &states, State *result, int *emblemsCount, bool *haveInvisibleTags, const QColor &backgroundColor) { *result = State(); // Reset to default values. *emblemsCount = 0; *haveInvisibleTags = false; for (List::const_iterator it = states.begin(); it != states.end(); ++it) { State *state = *it; bool isVisible = false; // For each property, if that properties have a value (is not default) is the current state of the list, // and if it haven't been set to the result state by a previous state, then it's visible and we assign the property to the result state. if (!state->emblem().isEmpty()) { ++*emblemsCount; isVisible = true; } if (state->bold() && !result->bold()) { result->setBold(true); isVisible = true; } if (state->italic() && !result->italic()) { result->setItalic(true); isVisible = true; } if (state->underline() && !result->underline()) { result->setUnderline(true); isVisible = true; } if (state->strikeOut() && !result->strikeOut()) { result->setStrikeOut(true); isVisible = true; } if (state->textColor().isValid() && !result->textColor().isValid()) { result->setTextColor(state->textColor()); isVisible = true; } if (!state->fontName().isEmpty() && result->fontName().isEmpty()) { result->setFontName(state->fontName()); isVisible = true; } if (state->fontSize() > 0 && result->fontSize() <= 0) { result->setFontSize(state->fontSize()); isVisible = true; } if (state->backgroundColor().isValid() && !result->backgroundColor().isValid() && state->backgroundColor() != backgroundColor) { // vv result->setBackgroundColor(state->backgroundColor()); // This is particular: if the note background color is the same as the basket one, don't use that. isVisible = true; } // If it's not visible, well, at least one tag is not visible: the note will display "..." at the tags arrow place to show that: if (!isVisible) *haveInvisibleTags = true; } } void State::copyTo(State *other) { other->m_id = m_id; other->m_name = m_name; other->m_emblem = m_emblem; other->m_bold = m_bold; other->m_italic = m_italic; other->m_underline = m_underline; other->m_strikeOut = m_strikeOut; other->m_textColor = m_textColor; other->m_fontName = m_fontName; other->m_fontSize = m_fontSize; other->m_backgroundColor = m_backgroundColor; other->m_textEquivalent = m_textEquivalent; other->m_onAllTextLines = m_onAllTextLines; // TODO other->m_allowCrossReferences = m_allowCrossReferences; // TODO: other->m_parentTag; } /** class Tag: */ Tag::List Tag::all = Tag::List(); long Tag::nextStateUid = 1; long Tag::getNextStateUid() { return nextStateUid++; // Return the next Uid and THEN increment the Uid } Tag::Tag() { static int tagNumber = 0; ++tagNumber; QString sAction = "tag_shortcut_number_" + QString::number(tagNumber); KActionCollection *ac = Global::bnpView->actionCollection(); m_action = ac->addAction(sAction, Global::bnpView, SLOT(activatedTagShortcut())); m_action->setText("FAKE TEXT"); m_action->setIcon(QIcon::fromTheme("FAKE ICON")); ac->setShortcutsConfigurable(m_action, false); // We do it in the tag properties dialog m_inheritedBySiblings = false; } Tag::~Tag() { delete m_action; } void Tag::setName(const QString &name) { m_name = name; m_action->setText("TAG SHORTCUT: " + name); // TODO: i18n (for debug purpose only by now). } State *Tag::stateForId(const QString &id) { for (List::iterator it = all.begin(); it != all.end(); ++it) for (State::List::iterator it2 = (*it)->states().begin(); it2 != (*it)->states().end(); ++it2) if ((*it2)->id() == id) return *it2; return 0; } Tag *Tag::tagForKAction(QAction *action) { for (List::iterator it = all.begin(); it != all.end(); ++it) if ((*it)->m_action == action) return *it; return 0; } QMap Tag::loadTags(const QString &path /* = QString()*/ /*, bool merge = false*/) { QMap mergedStates; bool merge = !path.isEmpty(); QString fullPath = (merge ? path : Global::savesFolder() + "tags.xml"); QString doctype = "basketTags"; QDir dir; if (!dir.exists(fullPath)) { if (merge) return mergedStates; DEBUG_WIN << "Tags file does not exist: Creating it..."; createDefaultTagsSet(fullPath); } QScopedPointer document(XMLWork::openFile(doctype, fullPath)); if (!document) { DEBUG_WIN << "FAILED to read the tags file"; return mergedStates; } QDomElement docElem = document->documentElement(); if (!merge) nextStateUid = docElem.attribute("nextStateUid", QString::number(nextStateUid)).toLong(); QDomNode node = docElem.firstChild(); while (!node.isNull()) { QDomElement element = node.toElement(); if ((!element.isNull()) && element.tagName() == "tag") { Tag *tag = new Tag(); // Load properties: QString name = XMLWork::getElementText(element, "name"); QString shortcut = XMLWork::getElementText(element, "shortcut"); QString inherited = XMLWork::getElementText(element, "inherited", "false"); tag->setName(name); tag->setShortcut(QKeySequence(shortcut)); tag->setInheritedBySiblings(XMLWork::trueOrFalse(inherited)); // Load states: QDomNode subNode = element.firstChild(); while (!subNode.isNull()) { QDomElement subElement = subNode.toElement(); if ((!subElement.isNull()) && subElement.tagName() == "state") { State *state = new State(subElement.attribute("id"), tag); state->setName(XMLWork::getElementText(subElement, "name")); state->setEmblem(XMLWork::getElementText(subElement, "emblem")); QDomElement textElement = XMLWork::getElement(subElement, "text"); state->setBold(XMLWork::trueOrFalse(textElement.attribute("bold", "false"))); state->setItalic(XMLWork::trueOrFalse(textElement.attribute("italic", "false"))); state->setUnderline(XMLWork::trueOrFalse(textElement.attribute("underline", "false"))); state->setStrikeOut(XMLWork::trueOrFalse(textElement.attribute("strikeOut", "false"))); QString textColor = textElement.attribute("color", ""); state->setTextColor(textColor.isEmpty() ? QColor() : QColor(textColor)); QDomElement fontElement = XMLWork::getElement(subElement, "font"); state->setFontName(fontElement.attribute("name", "")); QString fontSize = fontElement.attribute("size", ""); state->setFontSize(fontSize.isEmpty() ? -1 : fontSize.toInt()); QString backgroundColor = XMLWork::getElementText(subElement, "backgroundColor", ""); state->setBackgroundColor(backgroundColor.isEmpty() ? QColor() : QColor(backgroundColor)); QDomElement textEquivalentElement = XMLWork::getElement(subElement, "textEquivalent"); state->setTextEquivalent(textEquivalentElement.attribute("string", "")); state->setOnAllTextLines(XMLWork::trueOrFalse(textEquivalentElement.attribute("onAllTextLines", "false"))); QString allowXRef = XMLWork::getElementText(subElement, "allowCrossReferences", "true"); state->setAllowCrossReferences(XMLWork::trueOrFalse(allowXRef)); tag->appendState(state); } subNode = subNode.nextSibling(); } // If the Tag is Valid: if (tag->countStates() > 0) { // Rename Things if Needed: State *firstState = tag->states().first(); if (tag->countStates() == 1 && firstState->name().isEmpty()) firstState->setName(tag->name()); if (tag->name().isEmpty()) tag->setName(firstState->name()); // Add or Merge the Tag: if (!merge) { all.append(tag); } else { Tag *similarTag = tagSimilarTo(tag); // Tag does not exists, add it: if (similarTag == 0) { // We are merging the new states, so we should choose new and unique (on that computer) ids for those states: for (State::List::iterator it = tag->states().begin(); it != tag->states().end(); ++it) { State *state = *it; QString uid = state->id(); QString newUid = "tag_state_" + QString::number(getNextStateUid()); state->setId(newUid); mergedStates[uid] = newUid; } // TODO: if shortcut is already assigned to a previous note, do not import it, keep the user settings! all.append(tag); // Tag already exists, rename to their ids: } else { State::List::iterator it2 = similarTag->states().begin(); for (State::List::iterator it = tag->states().begin(); it != tag->states().end(); ++it, ++it2) { State *state = *it; State *similarState = *it2; QString uid = state->id(); QString newUid = similarState->id(); if (uid != newUid) mergedStates[uid] = newUid; } delete tag; // Already exists, not to be merged. Delete the shortcut and all. } } } } node = node.nextSibling(); } return mergedStates; } Tag *Tag::tagSimilarTo(Tag *tagToTest) { // Tags are considered similar if they have the same name, the same number of states, in the same order, and the same look. // Keyboard shortcut, text equivalent and onEveryLines are user settings, and thus not considered during the comparison. // Default tags (To Do, Important, Idea...) do not take into account the name of the tag and states during the comparison. // Default tags are equal only if they have the same number of states, in the same order, and the same look. // This is because default tag names are translated differently in every countries, but they are essentially the same! // User tags begins with "tag_state_" followed by a number. Default tags are the other ones. // Browse all tags: for (List::iterator it = all.begin(); it != all.end(); ++it) { Tag *tag = *it; bool same = true; bool sameName; bool defaultTag = true; // We test only name and look. Shortcut and whenever it is inherited by sibling new notes are user settings only! sameName = tag->name() == tagToTest->name(); if (tag->countStates() != tagToTest->countStates()) continue; // Tag is different! // We found a tag with same name, check if every states/look are same too: State::List::iterator itTest = tagToTest->states().begin(); for (State::List::iterator it2 = (*it)->states().begin(); it2 != (*it)->states().end(); ++it2, ++itTest) { State *state = *it2; State *stateToTest = *itTest; if (state->id().startsWith(QLatin1String("tag_state_")) || stateToTest->id().startsWith(QLatin1String("tag_state_"))) { defaultTag = false; } if (state->name() != stateToTest->name()) { sameName = false; } if (state->emblem() != stateToTest->emblem()) { same = false; break; } if (state->bold() != stateToTest->bold()) { same = false; break; } if (state->italic() != stateToTest->italic()) { same = false; break; } if (state->underline() != stateToTest->underline()) { same = false; break; } if (state->strikeOut() != stateToTest->strikeOut()) { same = false; break; } if (state->textColor() != stateToTest->textColor()) { same = false; break; } if (state->fontName() != stateToTest->fontName()) { same = false; break; } if (state->fontSize() != stateToTest->fontSize()) { same = false; break; } if (state->backgroundColor() != stateToTest->backgroundColor()) { same = false; break; } // Text equivalent (as well as onAllTextLines) is also a user setting! } // We found an existing tag that is "exactly" the same: if (same && (sameName || defaultTag)) return tag; } // Not found: return 0; } void Tag::saveTags() { DEBUG_WIN << "Saving tags..."; saveTagsTo(all, Global::savesFolder() + "tags.xml"); GitWrapper::commitTagsXml(); } void Tag::saveTagsTo(QList &list, const QString &fullPath) { // Create Document: QDomDocument document(/*doctype=*/"basketTags"); QDomElement root = document.createElement("basketTags"); root.setAttribute("nextStateUid", static_cast(nextStateUid)); document.appendChild(root); // Save all tags: for (List::iterator it = list.begin(); it != list.end(); ++it) { Tag *tag = *it; // Create tag node: QDomElement tagNode = document.createElement("tag"); root.appendChild(tagNode); // Save tag properties: XMLWork::addElement(document, tagNode, "name", tag->name()); XMLWork::addElement(document, tagNode, "shortcut", tag->shortcut().toString()); XMLWork::addElement(document, tagNode, "inherited", XMLWork::trueOrFalse(tag->inheritedBySiblings())); // Save all states: for (State::List::iterator it2 = (*it)->states().begin(); it2 != (*it)->states().end(); ++it2) { State *state = *it2; // Create state node: QDomElement stateNode = document.createElement("state"); tagNode.appendChild(stateNode); // Save state properties: stateNode.setAttribute("id", state->id()); XMLWork::addElement(document, stateNode, "name", state->name()); XMLWork::addElement(document, stateNode, "emblem", state->emblem()); QDomElement textNode = document.createElement("text"); stateNode.appendChild(textNode); QString textColor = (state->textColor().isValid() ? state->textColor().name() : ""); textNode.setAttribute("bold", XMLWork::trueOrFalse(state->bold())); textNode.setAttribute("italic", XMLWork::trueOrFalse(state->italic())); textNode.setAttribute("underline", XMLWork::trueOrFalse(state->underline())); textNode.setAttribute("strikeOut", XMLWork::trueOrFalse(state->strikeOut())); textNode.setAttribute("color", textColor); QDomElement fontNode = document.createElement("font"); stateNode.appendChild(fontNode); fontNode.setAttribute("name", state->fontName()); fontNode.setAttribute("size", state->fontSize()); QString backgroundColor = (state->backgroundColor().isValid() ? state->backgroundColor().name() : ""); XMLWork::addElement(document, stateNode, "backgroundColor", backgroundColor); QDomElement textEquivalentNode = document.createElement("textEquivalent"); stateNode.appendChild(textEquivalentNode); textEquivalentNode.setAttribute("string", state->textEquivalent()); textEquivalentNode.setAttribute("onAllTextLines", XMLWork::trueOrFalse(state->onAllTextLines())); XMLWork::addElement(document, stateNode, "allowCrossReferences", XMLWork::trueOrFalse(state->allowCrossReferences())); } } // Write to Disk: if (!BasketScene::safelySaveToFile(fullPath, "\n" + document.toString())) DEBUG_WIN << "FAILED to save tags!"; } void Tag::copyTo(Tag *other) { other->m_name = m_name; other->m_action->setShortcut(m_action->shortcut()); other->m_inheritedBySiblings = m_inheritedBySiblings; } void Tag::createDefaultTagsSet(const QString &fullPath) { QString xml = QString( "\n" "\n" " \n" " %1\n" // "To Do" " Ctrl+1\n" " true\n" " \n" " %2\n" // "Unchecked" " tag_checkbox\n" " \n" " \n" " \n" " \n" " \n" " \n" " %3\n" // "Done" " tag_checkbox_checked\n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" " \n" " %4\n" // "Progress" " Ctrl+2\n" " true\n" " \n" " %5\n" // "0 %" " tag_progress_000\n" " \n" " \n" " \n" " %6\n" // "25 %" " tag_progress_025\n" " \n" " \n" " \n" " %7\n" // "50 %" " tag_progress_050\n" " \n" " \n" " \n" " %8\n" // "75 %" " tag_progress_075\n" " \n" " \n" " \n" " %9\n" // "100 %" " tag_progress_100\n" " \n" " \n" " \n" "\n") .arg(i18n("To Do"), i18n("Unchecked"), i18n("Done")) // %1 %2 %3 .arg(i18n("Progress"), i18n("0 %"), i18n("25 %")) // %4 %5 %6 .arg(i18n("50 %"), i18n("75 %"), i18n("100 %")) // %7 %8 %9 + QString( " \n" " %1\n" // "Priority" " Ctrl+3\n" " true\n" " \n" " %2\n" // "Low" " tag_priority_low\n" " \n" " \n" " \n" " %3\n" // "Medium " tag_priority_medium\n" " \n" " \n" " \n" " %4\n" // "High" " tag_priority_high\n" " \n" " \n" " \n" "\n" " \n" " %5\n" // "Preference" " Ctrl+4\n" " true\n" " \n" " %6\n" // "Bad" " tag_preference_bad\n" " \n" " \n" " \n" " %7\n" // "Good" " tag_preference_good\n" " \n" " \n" " \n" " %8\n" // "Excellent" " tag_preference_excellent\n" " \n" " \n" " \n" "\n" " \n" " %9\n" // "Highlight" " Ctrl+5\n" " \n" " #ffffcc\n" " \" />\n" " \n" " \n" "\n") .arg(i18n("Priority"), i18n("Low"), i18n("Medium")) // %1 %2 %3 .arg(i18n("High"), i18n("Preference"), i18n("Bad")) // %4 %5 %6 .arg(i18n("Good"), i18n("Excellent"), i18n("Highlight")) // %7 %8 %9 + QString( " \n" " %1\n" // "Important" " Ctrl+6\n" " \n" " tag_important\n" " #ffcccc\n" " \n" " \n" " \n" "\n" " \n" " %2\n" // "Very Important" " Ctrl+7\n" " \n" " tag_important\n" " \n" " #ff0000\n" " \n" " \n" " \n" "\n" " \n" " %3\n" // "Information" " Ctrl+8\n" " \n" " dialog-information\n" " \n" " \n" " \n" "\n" " \n" " %4\n" // "Idea" " Ctrl+9\n" " \n" " ktip\n" " \n" // I. " \n" " " "\n" "\n" " \n" " %6\n" // "Title" " Ctrl+0\n" " \n" " \n" " \n" " \n" " \n" "\n" " \n" " %7\n" // "Code" " \n" " \n" " \n" " false\n" " \n" " \n" "\n" " \n" " \n" " %8\n" // "Work" " \n" " \n" // W. " \n" " " "\n" "\n") .arg(i18n("Important"), i18n("Very Important"), i18n("Information")) // %1 %2 %3 .arg(i18n("Idea"), i18nc("The initial of 'Idea'", "I."), i18n("Title")) // %4 %5 %6 .arg(i18n("Code"), i18n("Work"), i18nc("The initial of 'Work'", "W.")) // %7 %8 %9 + QString( " \n" " \n" " %1\n" // "Personal" " \n" " \n" // P. " \n" " \n" "\n" " \n" " \n" " %3\n" // "Funny" " tag_fun\n" " \n" " \n" "\n" "") .arg(i18n("Personal"), i18nc("The initial of 'Personal'", "P."), i18n("Funny")); // %1 %2 %3 // Write to Disk: QFile file(fullPath); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream.setCodec("UTF-8"); stream << "\n"; stream << xml; file.close(); } else DEBUG_WIN << "FAILED to create the tags file!"; } // StateAction StateAction::StateAction(State *state, const QKeySequence &shortcut, QWidget *parent, bool withTagName) : KToggleAction(parent) , m_state(state) { setText(m_state->name()); if (withTagName && m_state->parentTag()) setText(m_state->parentTag()->name()); setIcon(KIconLoader::global()->loadIcon(m_state->emblem(), KIconLoader::Small, 16, KIconLoader::DefaultState, QStringList(), /*path_store=*/0L, /*canReturnNull=*/true)); setShortcut(shortcut); } StateAction::~StateAction() { // pass } diff --git a/src/tag.h b/src/tag.h index 58ace9f..507a3d8 100644 --- a/src/tag.h +++ b/src/tag.h @@ -1,300 +1,287 @@ -/*************************************************************************** - * Copyright (C) 2005 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2005 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef TAG_H #define TAG_H #include #include class QColor; class QFont; class QPainter; class QString; class QKeySequence; class Tag; /** * @author Sébastien Laoût */ class State { public: /// LIST OF STATES: typedef QList List; public: /// CONSTRUCTOR AND DESTRUCTOR: explicit State(const QString &id = QString(), Tag *tag = 0); ~State(); /// SET PROPERTIES: void setId(const QString &id) { m_id = id; } void setName(const QString &name) { m_name = name; } void setEmblem(const QString &emblem) { m_emblem = emblem; } void setBold(bool bold) { m_bold = bold; } void setItalic(bool italic) { m_italic = italic; } void setUnderline(bool underline) { m_underline = underline; } void setStrikeOut(bool strikeOut) { m_strikeOut = strikeOut; } void setTextColor(const QColor &color) { m_textColor = color; } void setFontName(const QString &font) { m_fontName = font; } void setFontSize(int size) { m_fontSize = size; } void setBackgroundColor(const QColor &color) { m_backgroundColor = color; } void setTextEquivalent(const QString &text) { m_textEquivalent = text; } void setOnAllTextLines(bool yes) { m_onAllTextLines = yes; } void setAllowCrossReferences(bool yes) { m_allowCrossReferences = yes; } void setParentTag(Tag *tag) { m_parentTag = tag; } /// GET PROPERTIES: QString id() const { return m_id; } QString name() const { return m_name; } QString emblem() const { return m_emblem; } bool bold() const { return m_bold; } bool italic() const { return m_italic; } bool underline() const { return m_underline; } bool strikeOut() const { return m_strikeOut; } QColor textColor() const { return m_textColor; } QString fontName() const { return m_fontName; } int fontSize() const { return m_fontSize; } QColor backgroundColor() const { return m_backgroundColor; } QString textEquivalent() const { return m_textEquivalent; } bool onAllTextLines() const { return m_onAllTextLines; } bool allowCrossReferences() const { return m_allowCrossReferences; } Tag *parentTag() const { return m_parentTag; } /// HELPING FUNCTIONS: State *nextState(bool cycle = true); QString fullName(); QFont font(QFont base); QString toCSS(const QString &gradientFolderPath, const QString &gradientFolderName, const QFont &baseFont); static void merge(const List &states, State *result, int *emblemsCount, bool *haveInvisibleTags, const QColor &backgroundColor); void copyTo(State *other); private: /// PROPERTIES: QString m_id; QString m_name; QString m_emblem; bool m_bold; bool m_italic; bool m_underline; bool m_strikeOut; QColor m_textColor; QString m_fontName; int m_fontSize; QColor m_backgroundColor; QString m_textEquivalent; bool m_onAllTextLines; bool m_allowCrossReferences; Tag *m_parentTag; }; /** A Tag is a category of Notes. * A Note can have 0, 1 or more Tags. * A Tag can have a unique State or several States. * @author Sébastien Laoût */ class Tag { public: /// LIST OF ALL TAGS IN THE APPLICATION: typedef QList List; static Tag::List all; static State *stateForId(const QString &id); static Tag *tagForKAction(QAction *action); static Tag *tagSimilarTo(Tag *tagToTest); static QMap loadTags(const QString &path = QString() /*, bool merge = false*/); /// << Load the tags contained in the XML file @p path or those in the application settings if @p path isEmpty(). If @p merge is true and /// a tag with the id of a tag that should be loaded already exist, the tag will get a new id. Otherwise, the tag will be dismissed. static void saveTags(); static void saveTagsTo(QList &list, const QString &fullPath); static void createDefaultTagsSet(const QString &file); static long getNextStateUid(); private: static long nextStateUid; public: /// CONSTRUCTOR AND DESTRUCTOR: Tag(/*State *firstState, const QString &name, bool inheritedBySiblings*/); ~Tag(); /// SET PROPERTIES: void setName(const QString &name); void setShortcut(const QKeySequence &shortcut) { m_action->setShortcut(shortcut); } void setInheritedBySiblings(bool inherited) { m_inheritedBySiblings = inherited; } void appendState(State *state) { m_states.append(state); state->setParentTag(this); } void removeState(State *state) { m_states.removeOne(state); state->setParentTag(0); } /// GET PROPERTIES: QString name() const { return m_name; } QKeySequence shortcut() const { return m_action->shortcut(); } bool inheritedBySiblings() const { return m_inheritedBySiblings; } State::List &states() const { return (State::List &)m_states; } int countStates() const { return m_states.count(); } void copyTo(Tag *other); private: /// PROPERTIES: QString m_name; QAction *m_action; bool m_inheritedBySiblings; State::List m_states; }; #include #include /** An action that represents a State or a Tag * @author Kelvie Wong * Based off of StateMenuItem by Sébastien Laoût */ class StateAction : public KToggleAction { Q_OBJECT Q_DISABLE_COPY(StateAction); public: StateAction(State *state, const QKeySequence &shortcut, QWidget *parent, bool withTagName = false); ~StateAction() override; private: State *m_state; QString m_name; QString m_shortcut; }; #endif // TAG_H diff --git a/src/tagsedit.cpp b/src/tagsedit.cpp index 043519a..d7c867a 100644 --- a/src/tagsedit.cpp +++ b/src/tagsedit.cpp @@ -1,1301 +1,1288 @@ -/*************************************************************************** - * Copyright (C) 2005 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2005 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "tagsedit.h" #include #include #include #include #include #include #include #include #include //For m_tags->header() #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bnpview.h" #include "global.h" #include "kcolorcombo2.h" #include "tag.h" #include "variouswidgets.h" //For FontSizeCombo /** class StateCopy: */ StateCopy::StateCopy(State *old /* = 0*/) { oldState = old; newState = new State(); if (oldState) oldState->copyTo(newState); } StateCopy::~StateCopy() { delete newState; } void StateCopy::copyBack() { } /** class TagCopy: */ TagCopy::TagCopy(Tag *old /* = 0*/) { oldTag = old; newTag = new Tag(); if (oldTag) oldTag->copyTo(newTag); if (old) for (State::List::iterator it = old->states().begin(); it != old->states().end(); ++it) stateCopies.append(new StateCopy(*it)); else stateCopies.append(new StateCopy()); } TagCopy::~TagCopy() { delete newTag; } void TagCopy::copyBack() { } bool TagCopy::isMultiState() { return (stateCopies.count() > 1); } /** class TagListViewItem: */ TagListViewItem::TagListViewItem(QTreeWidget *parent, TagCopy *tagCopy) : QTreeWidgetItem(parent) , m_tagCopy(tagCopy) , m_stateCopy(0) { setText(0, tagCopy->newTag->name()); } TagListViewItem::TagListViewItem(QTreeWidgetItem *parent, TagCopy *tagCopy) : QTreeWidgetItem(parent) , m_tagCopy(tagCopy) , m_stateCopy(0) { setText(0, tagCopy->newTag->name()); } TagListViewItem::TagListViewItem(QTreeWidget *parent, QTreeWidgetItem *after, TagCopy *tagCopy) : QTreeWidgetItem(parent, after) , m_tagCopy(tagCopy) , m_stateCopy(0) { setText(0, tagCopy->newTag->name()); } TagListViewItem::TagListViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, TagCopy *tagCopy) : QTreeWidgetItem(parent, after) , m_tagCopy(tagCopy) , m_stateCopy(0) { setText(0, tagCopy->newTag->name()); } /* */ TagListViewItem::TagListViewItem(QTreeWidget *parent, StateCopy *stateCopy) : QTreeWidgetItem(parent) , m_tagCopy(0) , m_stateCopy(stateCopy) { setText(0, stateCopy->newState->name()); } TagListViewItem::TagListViewItem(QTreeWidgetItem *parent, StateCopy *stateCopy) : QTreeWidgetItem(parent) , m_tagCopy(0) , m_stateCopy(stateCopy) { setText(0, stateCopy->newState->name()); } TagListViewItem::TagListViewItem(QTreeWidget *parent, QTreeWidgetItem *after, StateCopy *stateCopy) : QTreeWidgetItem(parent, after) , m_tagCopy(0) , m_stateCopy(stateCopy) { setText(0, stateCopy->newState->name()); } TagListViewItem::TagListViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, StateCopy *stateCopy) : QTreeWidgetItem(parent, after) , m_tagCopy(0) , m_stateCopy(stateCopy) { setText(0, stateCopy->newState->name()); } /* */ TagListViewItem::~TagListViewItem() { } TagListViewItem *TagListViewItem::lastChild() { if (childCount() <= 0) return 0; return (TagListViewItem *)child(childCount() - 1); } bool TagListViewItem::isEmblemObligatory() { return m_stateCopy != 0; // It's a state of a multi-state } TagListViewItem *TagListViewItem::prevSibling() { TagListViewItem *item = this; int idx = 0; if (!parent()) { idx = treeWidget()->indexOfTopLevelItem(item); if (idx <= 0) return NULL; return (TagListViewItem *)treeWidget()->topLevelItem(idx - 1); } else { idx = parent()->indexOfChild(item); if (idx <= 0) return NULL; return (TagListViewItem *)parent()->child(idx - 1); } } TagListViewItem *TagListViewItem::nextSibling() { TagListViewItem *item = this; int idx = 0; if (!parent()) { idx = treeWidget()->indexOfTopLevelItem(item); if (idx >= treeWidget()->topLevelItemCount()) return NULL; return (TagListViewItem *)treeWidget()->topLevelItem(idx + 1); } else { idx = parent()->indexOfChild(item); if (idx >= parent()->childCount()) return NULL; return (TagListViewItem *)parent()->child(idx + 1); } } TagListViewItem *TagListViewItem::parent() const { return (TagListViewItem *)QTreeWidgetItem::parent(); } // TODO: TagListViewItem:: int TAG_ICON_SIZE = 16; int TAG_MARGIN = 1; int TagListViewItem::width(const QFontMetrics & /* fontMetrics */, const QTreeWidget * /*listView*/, int /* column */) const { return treeWidget()->width(); } void TagListViewItem::setup() { QString text = (m_tagCopy ? m_tagCopy->newTag->name() : m_stateCopy->newState->name()); State *state = (m_tagCopy ? m_tagCopy->stateCopies[0]->newState : m_stateCopy->newState); QFont font = state->font(treeWidget()->font()); setText(0, text); QBrush brush; bool withIcon = m_stateCopy || (m_tagCopy && !m_tagCopy->isMultiState()); brush.setColor(isSelected() ? qApp->palette().color(QPalette::Highlight) : (withIcon && state->backgroundColor().isValid() ? state->backgroundColor() : treeWidget()->viewport()->palette().color(treeWidget()->viewport()->backgroundRole()))); setBackground(1, brush); } /** class TagListView: */ TagListView::TagListView(QWidget *parent) : QTreeWidget(parent) { setItemDelegate(new TagListDelegate); } TagListView::~TagListView() { } void TagListView::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Delete) emit deletePressed(); else if (event->key() != Qt::Key_Left || (currentItem() && currentItem()->parent())) // Do not allow to open/close first-level items QTreeWidget::keyPressEvent(event); } void TagListView::mouseDoubleClickEvent(QMouseEvent *event) { // Ignore this event! Do not open/close first-level items! // But trigger edit (change focus to name) when double-click an item: if (itemAt(event->pos()) != 0) emit doubleClickedItem(); } void TagListView::mousePressEvent(QMouseEvent *event) { // When clicking on an empty space, QListView would unselect the current item! We forbid that! if (itemAt(event->pos()) != 0) QTreeWidget::mousePressEvent(event); } void TagListView::mouseReleaseEvent(QMouseEvent *event) { // When clicking on an empty space, QListView would unselect the current item! We forbid that! if (itemAt(event->pos()) != 0) QTreeWidget::mouseReleaseEvent(event); } TagListViewItem *TagListView::currentItem() const { return (TagListViewItem *)QTreeWidget::currentItem(); } TagListViewItem *TagListView::firstChild() const { if (topLevelItemCount() <= 0) return NULL; return (TagListViewItem *)topLevelItem(0); } TagListViewItem *TagListView::lastItem() const { if (topLevelItemCount() <= 0) return NULL; return (TagListViewItem *)topLevelItem(topLevelItemCount() - 1); } /** class TagsEditDialog: */ TagsEditDialog::TagsEditDialog(QWidget *parent, State *stateToEdit, bool addNewTag) : QDialog(parent) , m_loading(false) { // QDialog options setWindowTitle(i18n("Customize Tags")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); okButton->setDefault(true); setObjectName("CustomizeTags"); setModal(true); connect(okButton, SIGNAL(clicked()), SLOT(slotOk())); connect(buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), SLOT(slotCancel())); QHBoxLayout *layout = new QHBoxLayout(mainWidget); /* Left part: */ QPushButton *newTag = new QPushButton(i18n("Ne&w Tag"), mainWidget); QPushButton *newState = new QPushButton(i18n("New St&ate"), mainWidget); connect(newTag, SIGNAL(clicked()), this, SLOT(newTag())); connect(newState, SIGNAL(clicked()), this, SLOT(newState())); m_tags = new TagListView(mainWidget); m_tags->header()->hide(); m_tags->setRootIsDecorated(false); // m_tags->addColumn(""); // m_tags->setSorting(-1); // Sort column -1, so disabled sorting // m_tags->setResizeMode(QTreeWidget::LastColumn); m_tags->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_moveUp = new QPushButton(mainWidget); KGuiItem::assign(m_moveUp, KGuiItem("", "arrow-up")); m_moveDown = new QPushButton(mainWidget); KGuiItem::assign(m_moveDown, KGuiItem("", "arrow-down")); m_deleteTag = new QPushButton(mainWidget); KGuiItem::assign(m_deleteTag, KGuiItem("", "edit-delete")); m_moveUp->setToolTip(i18n("Move Up (Ctrl+Shift+Up)")); m_moveDown->setToolTip(i18n("Move Down (Ctrl+Shift+Down)")); m_deleteTag->setToolTip(i18n("Delete")); connect(m_moveUp, SIGNAL(clicked()), this, SLOT(moveUp())); connect(m_moveDown, SIGNAL(clicked()), this, SLOT(moveDown())); connect(m_deleteTag, SIGNAL(clicked()), this, SLOT(deleteTag())); QHBoxLayout *topLeftLayout = new QHBoxLayout; topLeftLayout->addWidget(m_moveUp); topLeftLayout->addWidget(m_moveDown); topLeftLayout->addWidget(m_deleteTag); QVBoxLayout *leftLayout = new QVBoxLayout; leftLayout->addWidget(newTag); leftLayout->addWidget(newState); leftLayout->addWidget(m_tags); leftLayout->addLayout(topLeftLayout); layout->addLayout(leftLayout); /* Right part: */ QWidget *rightWidget = new QWidget(mainWidget); m_tagBox = new QGroupBox(i18n("Tag"), rightWidget); m_tagBoxLayout = new QHBoxLayout; m_tagBox->setLayout(m_tagBoxLayout); QWidget *tagWidget = new QWidget; m_tagBoxLayout->addWidget(tagWidget); m_tagName = new QLineEdit(tagWidget); QLabel *tagNameLabel = new QLabel(i18n("&Name:"), tagWidget); tagNameLabel->setBuddy(m_tagName); m_shortcut = new KShortcutWidget(tagWidget); m_removeShortcut = new QPushButton(i18nc("Remove tag shortcut", "&Remove"), tagWidget); QLabel *shortcutLabel = new QLabel(i18n("S&hortcut:"), tagWidget); shortcutLabel->setBuddy(m_shortcut); // connect( m_shortcut, SIGNAL(shortcutChanged(const QKeySequence&)), this, SLOT(capturedShortcut(const QKeySequence&)) ); connect(m_removeShortcut, SIGNAL(clicked()), this, SLOT(removeShortcut())); m_inherit = new QCheckBox(i18n("&Inherited by new sibling notes"), tagWidget); m_allowCrossRefernce = new QCheckBox(i18n("Allow Cross Reference Links"), tagWidget); HelpLabel *allowCrossReferenceHelp = new HelpLabel(i18n("What does this do?"), "

    " + i18n("This option will enable you to type a cross reference link directly into a text note. Cross Reference links can have the following syntax:") + "

    " + "

    " + i18n("From the top of the tree (Absolute path):") + "
    " + i18n("[[/top level item/child|optional title]]") + "

    " + "

    " + i18n("Relative to the current basket:") + "
    " + i18n("[[../sibling|optional title]]") + "
    " + i18n("[[child|optional title]]") + "
    " + i18n("[[./child|optional title]]") + "

    ", tagWidget); QGridLayout *tagGrid = new QGridLayout(tagWidget); tagGrid->addWidget(tagNameLabel, 0, 0); tagGrid->addWidget(m_tagName, 0, 1, 1, 3); tagGrid->addWidget(shortcutLabel, 1, 0); tagGrid->addWidget(m_shortcut, 1, 1); tagGrid->addWidget(m_removeShortcut, 1, 2); tagGrid->addWidget(m_inherit, 2, 0, 1, 4); tagGrid->addWidget(m_allowCrossRefernce, 3, 0); tagGrid->addWidget(allowCrossReferenceHelp, 3, 1); tagGrid->setColumnStretch(/*col=*/3, /*stretch=*/255); m_stateBox = new QGroupBox(i18n("State"), rightWidget); m_stateBoxLayout = new QHBoxLayout; m_stateBox->setLayout(m_stateBoxLayout); QWidget *stateWidget = new QWidget; m_stateBoxLayout->addWidget(stateWidget); m_stateName = new QLineEdit(stateWidget); m_stateNameLabel = new QLabel(i18n("Na&me:"), stateWidget); m_stateNameLabel->setBuddy(m_stateName); QWidget *emblemWidget = new QWidget(stateWidget); m_emblem = new KIconButton(emblemWidget); m_emblem->setIconType(KIconLoader::NoGroup, KIconLoader::Action); m_emblem->setIconSize(16); m_emblem->setIcon("edit-delete"); m_removeEmblem = new QPushButton(i18nc("Remove tag emblem", "Remo&ve"), emblemWidget); QLabel *emblemLabel = new QLabel(i18n("&Emblem:"), stateWidget); emblemLabel->setBuddy(m_emblem); connect(m_removeEmblem, SIGNAL(clicked()), this, SLOT(removeEmblem())); // m_emblem.resetIcon() is not a slot! // Make the icon button and the remove button the same height: int height = qMax(m_emblem->sizeHint().width(), m_emblem->sizeHint().height()); height = qMax(height, m_removeEmblem->sizeHint().height()); m_emblem->setFixedSize(height, height); // Make it square m_removeEmblem->setFixedHeight(height); m_emblem->resetIcon(); QHBoxLayout *emblemLayout = new QHBoxLayout(emblemWidget); emblemLayout->addWidget(m_emblem); emblemLayout->addWidget(m_removeEmblem); emblemLayout->addStretch(); m_backgroundColor = new KColorCombo2(QColor(), palette().color(QPalette::Base), stateWidget); QLabel *backgroundColorLabel = new QLabel(i18n("&Background:"), stateWidget); backgroundColorLabel->setBuddy(m_backgroundColor); QHBoxLayout *backgroundColorLayout = new QHBoxLayout(0); backgroundColorLayout->addWidget(m_backgroundColor); backgroundColorLayout->addStretch(); QIcon boldIconSet = QIcon::fromTheme("format-text-bold"); m_bold = new QPushButton(boldIconSet, "", stateWidget); m_bold->setCheckable(true); int size = qMax(m_bold->sizeHint().width(), m_bold->sizeHint().height()); m_bold->setFixedSize(size, size); // Make it square! m_bold->setToolTip(i18n("Bold")); QIcon underlineIconSet = QIcon::fromTheme("format-text-underline"); m_underline = new QPushButton(underlineIconSet, "", stateWidget); m_underline->setCheckable(true); m_underline->setFixedSize(size, size); // Make it square! m_underline->setToolTip(i18n("Underline")); QIcon italicIconSet = QIcon::fromTheme("format-text-italic"); m_italic = new QPushButton(italicIconSet, "", stateWidget); m_italic->setCheckable(true); m_italic->setFixedSize(size, size); // Make it square! m_italic->setToolTip(i18n("Italic")); QIcon strikeIconSet = QIcon::fromTheme("format-text-strikethrough"); m_strike = new QPushButton(strikeIconSet, "", stateWidget); m_strike->setCheckable(true); m_strike->setFixedSize(size, size); // Make it square! m_strike->setToolTip(i18n("Strike Through")); QLabel *textLabel = new QLabel(i18n("&Text:"), stateWidget); textLabel->setBuddy(m_bold); QHBoxLayout *textLayout = new QHBoxLayout; textLayout->addWidget(m_bold); textLayout->addWidget(m_underline); textLayout->addWidget(m_italic); textLayout->addWidget(m_strike); textLayout->addStretch(); m_textColor = new KColorCombo2(QColor(), palette().color(QPalette::Text), stateWidget); QLabel *textColorLabel = new QLabel(i18n("Co&lor:"), stateWidget); textColorLabel->setBuddy(m_textColor); m_font = new QFontComboBox(stateWidget); m_font->addItem(i18n("(Default)"), 0); QLabel *fontLabel = new QLabel(i18n("&Font:"), stateWidget); fontLabel->setBuddy(m_font); m_fontSize = new FontSizeCombo(/*rw=*/true, /*withDefault=*/true, stateWidget); QLabel *fontSizeLabel = new QLabel(i18n("&Size:"), stateWidget); fontSizeLabel->setBuddy(m_fontSize); m_textEquivalent = new QLineEdit(stateWidget); QLabel *textEquivalentLabel = new QLabel(i18n("Te&xt equivalent:"), stateWidget); textEquivalentLabel->setBuddy(m_textEquivalent); QFont font = m_textEquivalent->font(); font.setFamily("monospace"); m_textEquivalent->setFont(font); HelpLabel *textEquivalentHelp = new HelpLabel(i18n("What is this for?"), "

    " + i18n("When you copy and paste or drag and drop notes to a text editor, this text will be inserted as a textual equivalent of the tag.") + "

    " + // "

    " + i18n("If filled, this property lets you paste this tag or this state as textual equivalent.") + "
    " + i18n("For instance, a list of notes with the To Do and Done tags are exported as lines preceded by [ ] or [x], " "representing an empty checkbox and a checked box.") + "

    " + "

    ", stateWidget); QHBoxLayout *textEquivalentHelpLayout = new QHBoxLayout; textEquivalentHelpLayout->addWidget(textEquivalentHelp); textEquivalentHelpLayout->addStretch(255); m_onEveryLines = new QCheckBox(i18n("On ever&y line"), stateWidget); HelpLabel *onEveryLinesHelp = new HelpLabel(i18n("What does this mean?"), "

    " + i18n("When a note has several lines, you can choose to export the tag or the state on the first line or on every line of the note.") + "

    " + "

    " + "

    " + i18n("In the example above, the tag of the top note is only exported on the first line, while the tag of the bottom note is exported on every line of the note."), stateWidget); QHBoxLayout *onEveryLinesHelpLayout = new QHBoxLayout; onEveryLinesHelpLayout->addWidget(onEveryLinesHelp); onEveryLinesHelpLayout->addStretch(255); QGridLayout *textEquivalentGrid = new QGridLayout; textEquivalentGrid->addWidget(textEquivalentLabel, 0, 0); textEquivalentGrid->addWidget(m_textEquivalent, 0, 1); textEquivalentGrid->addLayout(textEquivalentHelpLayout, 0, 2); textEquivalentGrid->addWidget(m_onEveryLines, 1, 1); textEquivalentGrid->addLayout(onEveryLinesHelpLayout, 1, 2); textEquivalentGrid->setColumnStretch(/*col=*/3, /*stretch=*/255); KSeparator *separator = new KSeparator(Qt::Horizontal, stateWidget); QGridLayout *stateGrid = new QGridLayout(stateWidget); stateGrid->addWidget(m_stateNameLabel, 0, 0); stateGrid->addWidget(m_stateName, 0, 1, 1, 6); stateGrid->addWidget(emblemLabel, 1, 0); stateGrid->addWidget(emblemWidget, 1, 1, 1, 6); stateGrid->addWidget(backgroundColorLabel, 1, 5); stateGrid->addLayout(backgroundColorLayout, 1, 6, 1, 1); stateGrid->addWidget(textLabel, 2, 0); stateGrid->addLayout(textLayout, 2, 1, 1, 4); stateGrid->addWidget(textColorLabel, 2, 5); stateGrid->addWidget(m_textColor, 2, 6); stateGrid->addWidget(fontLabel, 3, 0); stateGrid->addWidget(m_font, 3, 1, 1, 4); stateGrid->addWidget(fontSizeLabel, 3, 5); stateGrid->addWidget(m_fontSize, 3, 6); stateGrid->addWidget(separator, 4, 0, 1, 7); stateGrid->addLayout(textEquivalentGrid, 5, 0, 1, 7); QVBoxLayout *rightLayout = new QVBoxLayout(rightWidget); rightLayout->addWidget(m_tagBox); rightLayout->addWidget(m_stateBox); rightLayout->addStretch(); layout->addWidget(rightWidget); rightWidget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); // Equalize the width of the first column of the two grids: int maxWidth = tagNameLabel->sizeHint().width(); maxWidth = qMax(maxWidth, shortcutLabel->sizeHint().width()); maxWidth = qMax(maxWidth, m_stateNameLabel->sizeHint().width()); maxWidth = qMax(maxWidth, emblemLabel->sizeHint().width()); maxWidth = qMax(maxWidth, textLabel->sizeHint().width()); maxWidth = qMax(maxWidth, fontLabel->sizeHint().width()); maxWidth = qMax(maxWidth, backgroundColorLabel->sizeHint().width()); maxWidth = qMax(maxWidth, textEquivalentLabel->sizeHint().width()); tagNameLabel->setFixedWidth(maxWidth); m_stateNameLabel->setFixedWidth(maxWidth); textEquivalentLabel->setFixedWidth(maxWidth); // Load Tags: for (Tag::List::iterator tagIt = Tag::all.begin(); tagIt != Tag::all.end(); ++tagIt) m_tagCopies.append(new TagCopy(*tagIt)); TagListViewItem *lastInsertedItem = 0; TagListViewItem *lastInsertedSubItem; TagListViewItem *item; TagListViewItem *subItem; for (TagCopy::List::iterator tagCopyIt = m_tagCopies.begin(); tagCopyIt != m_tagCopies.end(); ++tagCopyIt) { // New List View Item: if (lastInsertedItem) item = new TagListViewItem(m_tags, lastInsertedItem, *tagCopyIt); else item = new TagListViewItem(m_tags, *tagCopyIt); item->setExpanded(true); lastInsertedItem = item; // Load if ((*tagCopyIt)->isMultiState()) { lastInsertedSubItem = 0; StateCopy::List stateCopies = item->tagCopy()->stateCopies; for (StateCopy::List::iterator stateCopyIt = stateCopies.begin(); stateCopyIt != stateCopies.end(); ++stateCopyIt) { if (lastInsertedSubItem) subItem = new TagListViewItem(item, lastInsertedSubItem, *stateCopyIt); else subItem = new TagListViewItem(item, *stateCopyIt); lastInsertedSubItem = subItem; } } } // Connect Signals: connect(m_tagName, SIGNAL(textChanged(const QString &)), this, SLOT(modified())); connect(m_shortcut, SIGNAL(shortcutChanged(const QList &)), this, SLOT(modified())); connect(m_inherit, SIGNAL(stateChanged(int)), this, SLOT(modified())); connect(m_allowCrossRefernce, SIGNAL(clicked(bool)), this, SLOT(modified())); connect(m_stateName, SIGNAL(textChanged(const QString &)), this, SLOT(modified())); connect(m_emblem, SIGNAL(iconChanged(QString)), this, SLOT(modified())); connect(m_backgroundColor, SIGNAL(changed(const QColor &)), this, SLOT(modified())); connect(m_bold, SIGNAL(toggled(bool)), this, SLOT(modified())); connect(m_underline, SIGNAL(toggled(bool)), this, SLOT(modified())); connect(m_italic, SIGNAL(toggled(bool)), this, SLOT(modified())); connect(m_strike, SIGNAL(toggled(bool)), this, SLOT(modified())); connect(m_textColor, SIGNAL(activated(int)), this, SLOT(modified())); connect(m_font, SIGNAL(editTextChanged(const QString &)), this, SLOT(modified())); connect(m_fontSize, SIGNAL(editTextChanged(const QString &)), this, SLOT(modified())); connect(m_textEquivalent, SIGNAL(textChanged(const QString &)), this, SLOT(modified())); connect(m_onEveryLines, SIGNAL(stateChanged(int)), this, SLOT(modified())); connect(m_tags, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *))); connect(m_tags, SIGNAL(deletePressed()), this, SLOT(deleteTag())); connect(m_tags, SIGNAL(doubleClickedItem()), this, SLOT(renameIt())); QTreeWidgetItem *firstItem = m_tags->firstChild(); if (stateToEdit != 0) { TagListViewItem *item = itemForState(stateToEdit); if (item) firstItem = item; } // Select the first tag unless the first tag is a multi-state tag. // In this case, select the first state, as it let customize the state AND the associated tag. if (firstItem) { if (firstItem->childCount() > 0) firstItem = firstItem->child(0); firstItem->setSelected(true); m_tags->setCurrentItem(firstItem); currentItemChanged(firstItem); if (stateToEdit == 0) m_tags->scrollToItem(firstItem); m_tags->setFocus(); } else { m_moveUp->setEnabled(false); m_moveDown->setEnabled(false); m_deleteTag->setEnabled(false); m_tagBox->setEnabled(false); m_stateBox->setEnabled(false); } // TODO: Disabled both boxes if no tag!!! // Some keyboard shortcuts: // Ctrl+arrows instead of Alt+arrows (same as Go menu in the main window) because Alt+Down is for combo boxes QAction *selectAbove = new QAction(this); selectAbove->setShortcut(Qt::CTRL + Qt::Key_Up); connect(selectAbove, SIGNAL(triggered()), this, SLOT(selectUp())); QAction *selectBelow = new QAction(this); selectBelow->setShortcut(Qt::CTRL + Qt::Key_Down); connect(selectBelow, SIGNAL(triggered()), this, SLOT(selectDown())); QAction *selectLeft = new QAction(this); selectLeft->setShortcut(Qt::CTRL + Qt::Key_Left); connect(selectLeft, SIGNAL(triggered()), this, SLOT(selectLeft())); QAction *selectRight = new QAction(this); selectRight->setShortcut(Qt::CTRL + Qt::Key_Right); connect(selectRight, SIGNAL(triggered()), this, SLOT(selectRight())); QAction *moveAbove = new QAction(this); moveAbove->setShortcut(Qt::CTRL + Qt::Key_Up); connect(moveAbove, SIGNAL(triggered()), this, SLOT(moveUp())); QAction *moveBelow = new QAction(this); moveBelow->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Down); connect(moveBelow, SIGNAL(triggered()), this, SLOT(moveDown())); QAction *rename = new QAction(this); rename->setShortcut(Qt::Key_F2); connect(rename, SIGNAL(triggered()), this, SLOT(renameIt())); m_tags->setMinimumSize(m_tags->sizeHint().width() * 2, m_tagBox->sizeHint().height() + m_stateBox->sizeHint().height()); if (addNewTag) QTimer::singleShot(0, this, SLOT(newTag())); else // Once the window initial size is computed and the window show, allow the user to resize it down: QTimer::singleShot(0, this, SLOT(resetTreeSizeHint())); } TagsEditDialog::~TagsEditDialog() { } void TagsEditDialog::resetTreeSizeHint() { m_tags->setMinimumSize(m_tags->sizeHint()); } TagListViewItem *TagsEditDialog::itemForState(State *state) { // Browse all tags: QTreeWidgetItemIterator it(m_tags); while (*it) { QTreeWidgetItem *item = *it; // Return if we found the tag item: TagListViewItem *tagItem = (TagListViewItem *)item; if (tagItem->tagCopy() && tagItem->tagCopy()->oldTag && tagItem->tagCopy()->stateCopies[0]->oldState == state) return tagItem; // Browser all sub-states: QTreeWidgetItemIterator it2(item); while (*it2) { QTreeWidgetItem *subItem = *it2; // Return if we found the state item: TagListViewItem *stateItem = (TagListViewItem *)subItem; if (stateItem->stateCopy() && stateItem->stateCopy()->oldState && stateItem->stateCopy()->oldState == state) return stateItem; ++it2; } ++it; } return 0; } void TagsEditDialog::newTag() { // Add to the "model": TagCopy *newTagCopy = new TagCopy(); newTagCopy->stateCopies[0]->newState->setId("tag_state_" + QString::number(Tag::getNextStateUid())); // TODO: Check if it's really unique m_tagCopies.append(newTagCopy); m_addedStates.append(newTagCopy->stateCopies[0]->newState); // Add to the "view": TagListViewItem *item; if (m_tags->firstChild()) { // QListView::lastItem is the last item in the tree. If we the last item is a state item, the new tag gets appended to the begin of the list. TagListViewItem *last = m_tags->lastItem(); if (last->parent()) last = last->parent(); item = new TagListViewItem(m_tags, last, newTagCopy); } else item = new TagListViewItem(m_tags, newTagCopy); m_deleteTag->setEnabled(true); m_tagBox->setEnabled(true); // Add to the "controller": m_tags->setCurrentItem(item); currentItemChanged(item); item->setSelected(true); m_tagName->setFocus(); } void TagsEditDialog::newState() { TagListViewItem *tagItem = m_tags->currentItem(); if (tagItem->parent()) tagItem = ((TagListViewItem *)(tagItem->parent())); tagItem->setExpanded(true); // Show sub-states if we're adding them for the first time! State *firstState = tagItem->tagCopy()->stateCopies[0]->newState; // Add the first state to the "view". From now on, it's a multi-state tag: if (tagItem->childCount() <= 0) { firstState->setName(tagItem->tagCopy()->newTag->name()); // Force emblem to exists for multi-state tags: if (firstState->emblem().isEmpty()) firstState->setEmblem("empty"); new TagListViewItem(tagItem, tagItem->tagCopy()->stateCopies[0]); } // Add to the "model": StateCopy *newStateCopy = new StateCopy(); firstState->copyTo(newStateCopy->newState); newStateCopy->newState->setId("tag_state_" + QString::number(Tag::getNextStateUid())); // TODO: Check if it's really unique newStateCopy->newState->setName(""); // We copied it too but it's likely the name will not be the same tagItem->tagCopy()->stateCopies.append(newStateCopy); m_addedStates.append(newStateCopy->newState); // Add to the "view": TagListViewItem *item = new TagListViewItem(tagItem, tagItem->lastChild(), newStateCopy); // Add to the "controller": m_tags->setCurrentItem(item); currentItemChanged(item); m_stateName->setFocus(); } void TagsEditDialog::moveUp() { if (!m_moveUp->isEnabled()) // Trggered by keyboard shortcut return; TagListViewItem *tagItem = m_tags->currentItem(); // Move in the list view: int idx = 0; QList children; if (tagItem->parent()) { TagListViewItem *parentItem = tagItem->parent(); idx = parentItem->indexOfChild(tagItem); if (idx > 0) { tagItem = (TagListViewItem *)parentItem->takeChild(idx); children = tagItem->takeChildren(); parentItem->insertChild(idx - 1, tagItem); tagItem->insertChildren(0, children); tagItem->setExpanded(true); } } else { idx = m_tags->indexOfTopLevelItem(tagItem); if (idx > 0) { tagItem = (TagListViewItem *)m_tags->takeTopLevelItem(idx); children = tagItem->takeChildren(); m_tags->insertTopLevelItem(idx - 1, tagItem); tagItem->insertChildren(0, children); tagItem->setExpanded(true); } } m_tags->setCurrentItem(tagItem); // Move in the value list: if (tagItem->tagCopy()) { int pos = m_tagCopies.indexOf(tagItem->tagCopy()); m_tagCopies.removeAll(tagItem->tagCopy()); int i = 0; for (TagCopy::List::iterator it = m_tagCopies.begin(); it != m_tagCopies.end(); ++it, ++i) if (i == pos - 1) { m_tagCopies.insert(it, tagItem->tagCopy()); break; } } else { StateCopy::List &stateCopies = ((TagListViewItem *)(tagItem->parent()))->tagCopy()->stateCopies; int pos = stateCopies.indexOf(tagItem->stateCopy()); stateCopies.removeAll(tagItem->stateCopy()); int i = 0; for (StateCopy::List::iterator it = stateCopies.begin(); it != stateCopies.end(); ++it, ++i) if (i == pos - 1) { stateCopies.insert(it, tagItem->stateCopy()); break; } } ensureCurrentItemVisible(); m_moveDown->setEnabled(tagItem->nextSibling() != 0); m_moveUp->setEnabled(tagItem->prevSibling() != 0); } void TagsEditDialog::moveDown() { if (!m_moveDown->isEnabled()) // Trggered by keyboard shortcut return; TagListViewItem *tagItem = m_tags->currentItem(); // Move in the list view: int idx = 0; QList children; if (tagItem->parent()) { TagListViewItem *parentItem = tagItem->parent(); idx = parentItem->indexOfChild(tagItem); if (idx < parentItem->childCount() - 1) { children = tagItem->takeChildren(); tagItem = (TagListViewItem *)parentItem->takeChild(idx); parentItem->insertChild(idx + 1, tagItem); tagItem->insertChildren(0, children); tagItem->setExpanded(true); } } else { idx = m_tags->indexOfTopLevelItem(tagItem); if (idx < m_tags->topLevelItemCount() - 1) { children = tagItem->takeChildren(); tagItem = (TagListViewItem *)m_tags->takeTopLevelItem(idx); m_tags->insertTopLevelItem(idx + 1, tagItem); tagItem->insertChildren(0, children); tagItem->setExpanded(true); } } m_tags->setCurrentItem(tagItem); // Move in the value list: if (tagItem->tagCopy()) { uint pos = m_tagCopies.indexOf(tagItem->tagCopy()); m_tagCopies.removeAll(tagItem->tagCopy()); if (pos == (uint)m_tagCopies.count() - 1) // Insert at end: iterator does not go there m_tagCopies.append(tagItem->tagCopy()); else { uint i = 0; for (TagCopy::List::iterator it = m_tagCopies.begin(); it != m_tagCopies.end(); ++it, ++i) if (i == pos + 1) { m_tagCopies.insert(it, tagItem->tagCopy()); break; } } } else { StateCopy::List &stateCopies = ((TagListViewItem *)(tagItem->parent()))->tagCopy()->stateCopies; uint pos = stateCopies.indexOf(tagItem->stateCopy()); stateCopies.removeAll(tagItem->stateCopy()); if (pos == (uint)stateCopies.count() - 1) // Insert at end: iterator does not go there stateCopies.append(tagItem->stateCopy()); else { uint i = 0; for (StateCopy::List::iterator it = stateCopies.begin(); it != stateCopies.end(); ++it, ++i) if (i == pos + 1) { stateCopies.insert(it, tagItem->stateCopy()); break; } } } ensureCurrentItemVisible(); m_moveDown->setEnabled(tagItem->nextSibling() != 0); m_moveUp->setEnabled(tagItem->prevSibling() != 0); } void TagsEditDialog::selectUp() { QKeyEvent *keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, 0, 0); QApplication::postEvent(m_tags, keyEvent); } void TagsEditDialog::selectDown() { QKeyEvent *keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, 0, 0); QApplication::postEvent(m_tags, keyEvent); } void TagsEditDialog::selectLeft() { QKeyEvent *keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Left, 0, 0); QApplication::postEvent(m_tags, keyEvent); } void TagsEditDialog::selectRight() { QKeyEvent *keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Right, 0, 0); QApplication::postEvent(m_tags, keyEvent); } void TagsEditDialog::deleteTag() { if (!m_deleteTag->isEnabled()) return; TagListViewItem *item = m_tags->currentItem(); int result = KMessageBox::Continue; if (item->tagCopy() && item->tagCopy()->oldTag) result = KMessageBox::warningContinueCancel(this, i18n("Deleting the tag will remove it from every note it is currently assigned to."), i18n("Confirm Delete Tag"), KGuiItem(i18n("Delete Tag"), "edit-delete")); else if (item->stateCopy() && item->stateCopy()->oldState) result = KMessageBox::warningContinueCancel(this, i18n("Deleting the state will remove the tag from every note the state is currently assigned to."), i18n("Confirm Delete State"), KGuiItem(i18n("Delete State"), "edit-delete")); if (result != KMessageBox::Continue) return; if (item->tagCopy()) { StateCopy::List stateCopies = item->tagCopy()->stateCopies; for (StateCopy::List::iterator stateCopyIt = stateCopies.begin(); stateCopyIt != stateCopies.end(); ++stateCopyIt) { StateCopy *stateCopy = *stateCopyIt; if (stateCopy->oldState) { m_deletedStates.append(stateCopy->oldState); m_addedStates.removeAll(stateCopy->oldState); } m_addedStates.removeAll(stateCopy->newState); } m_tagCopies.removeAll(item->tagCopy()); // Remove the new tag, to avoid keyboard-shortcut clashes: delete item->tagCopy()->newTag; } else { TagListViewItem *parentItem = item->parent(); // Remove the state: parentItem->tagCopy()->stateCopies.removeAll(item->stateCopy()); if (item->stateCopy()->oldState) { m_deletedStates.append(item->stateCopy()->oldState); m_addedStates.removeAll(item->stateCopy()->oldState); } m_addedStates.removeAll(item->stateCopy()->newState); delete item; item = 0; // Transform to single-state tag if needed: if (parentItem->childCount() == 1) { delete parentItem->child(0); m_tags->setCurrentItem(parentItem); } } delete item; if (m_tags->currentItem()) m_tags->currentItem()->setSelected(true); if (!m_tags->firstChild()) { m_deleteTag->setEnabled(false); m_tagBox->setEnabled(false); m_stateBox->setEnabled(false); } } void TagsEditDialog::renameIt() { if (m_tags->currentItem()->tagCopy()) m_tagName->setFocus(); else m_stateName->setFocus(); } void TagsEditDialog::capturedShortcut(const QKeySequence &shortcut) { Q_UNUSED(shortcut); // TODO: Validate it! // m_shortcut->setShortcut(shortcut); } void TagsEditDialog::removeShortcut() { // m_shortcut->setShortcut(QKeySequence()); modified(); } void TagsEditDialog::removeEmblem() { m_emblem->resetIcon(); modified(); } void TagsEditDialog::modified() { if (m_loading) return; TagListViewItem *tagItem = m_tags->currentItem(); if (tagItem == 0) return; if (tagItem->tagCopy()) { if (tagItem->tagCopy()->isMultiState()) { saveTagTo(tagItem->tagCopy()->newTag); } else { saveTagTo(tagItem->tagCopy()->newTag); saveStateTo(tagItem->tagCopy()->stateCopies[0]->newState); } } else if (tagItem->stateCopy()) { saveTagTo(((TagListViewItem *)(tagItem->parent()))->tagCopy()->newTag); saveStateTo(tagItem->stateCopy()->newState); } m_tags->currentItem()->setup(); if (m_tags->currentItem()->parent()) m_tags->currentItem()->parent()->setup(); m_removeShortcut->setEnabled(!m_shortcut->shortcut().isEmpty()); m_removeEmblem->setEnabled(!m_emblem->icon().isEmpty() && !m_tags->currentItem()->isEmblemObligatory()); m_onEveryLines->setEnabled(!m_textEquivalent->text().isEmpty()); } void TagsEditDialog::currentItemChanged(QTreeWidgetItem *item, QTreeWidgetItem *nextItem) { Q_UNUSED(nextItem); if (item == 0) return; m_loading = true; TagListViewItem *tagItem = (TagListViewItem *)item; if (tagItem->tagCopy()) { if (tagItem->tagCopy()->isMultiState()) { loadTagFrom(tagItem->tagCopy()->newTag); loadBlankState(); m_stateBox->setEnabled(false); m_stateBox->setTitle(i18n("State")); m_stateNameLabel->setEnabled(true); m_stateName->setEnabled(true); } else { loadTagFrom(tagItem->tagCopy()->newTag); // TODO: No duplicate loadStateFrom(tagItem->tagCopy()->stateCopies[0]->newState); m_stateBox->setEnabled(true); m_stateBox->setTitle(i18n("Appearance")); m_stateName->setText(""); m_stateNameLabel->setEnabled(false); m_stateName->setEnabled(false); } } else if (tagItem->stateCopy()) { loadTagFrom(((TagListViewItem *)(tagItem->parent()))->tagCopy()->newTag); loadStateFrom(tagItem->stateCopy()->newState); m_stateBox->setEnabled(true); m_stateBox->setTitle(i18n("State")); m_stateNameLabel->setEnabled(true); m_stateName->setEnabled(true); } ensureCurrentItemVisible(); m_loading = false; } void TagsEditDialog::ensureCurrentItemVisible() { TagListViewItem *tagItem = m_tags->currentItem(); // Ensure the tag and the states (if available) to be visible, but if there is a looooot of states, // ensure the tag is still visible, even if the last states are not... m_tags->scrollToItem(tagItem); int idx = 0; if (tagItem->parent()) { idx = ((QTreeWidgetItem *)tagItem->parent())->indexOfChild(tagItem); m_moveDown->setEnabled(idx < ((QTreeWidgetItem *)tagItem->parent())->childCount()); } else { idx = m_tags->indexOfTopLevelItem(tagItem); m_moveDown->setEnabled(idx < m_tags->topLevelItemCount()); } m_moveUp->setEnabled(idx > 0); } void TagsEditDialog::loadBlankState() { QFont defaultFont; m_stateName->setText(""); m_emblem->resetIcon(); m_removeEmblem->setEnabled(false); m_backgroundColor->setColor(QColor()); m_bold->setChecked(false); m_underline->setChecked(false); m_italic->setChecked(false); m_strike->setChecked(false); m_textColor->setColor(QColor()); // m_font->setCurrentIndex(0); m_font->setCurrentFont(defaultFont.family()); m_fontSize->setCurrentIndex(0); m_textEquivalent->setText(""); m_onEveryLines->setChecked(false); m_allowCrossRefernce->setChecked(false); } void TagsEditDialog::loadStateFrom(State *state) { m_stateName->setText(state->name()); if (state->emblem().isEmpty()) m_emblem->resetIcon(); else m_emblem->setIcon(state->emblem()); m_removeEmblem->setEnabled(!state->emblem().isEmpty() && !m_tags->currentItem()->isEmblemObligatory()); m_backgroundColor->setColor(state->backgroundColor()); m_bold->setChecked(state->bold()); m_underline->setChecked(state->underline()); m_italic->setChecked(state->italic()); m_strike->setChecked(state->strikeOut()); m_textColor->setColor(state->textColor()); m_textEquivalent->setText(state->textEquivalent()); m_onEveryLines->setChecked(state->onAllTextLines()); m_allowCrossRefernce->setChecked(state->allowCrossReferences()); QFont defaultFont; if (state->fontName().isEmpty()) m_font->setCurrentFont(defaultFont.family()); else m_font->setCurrentFont(state->fontName()); if (state->fontSize() == -1) m_fontSize->setCurrentIndex(0); else m_fontSize->setItemText(m_fontSize->currentIndex(), QString::number(state->fontSize())); } void TagsEditDialog::loadTagFrom(Tag *tag) { m_tagName->setText(tag->name()); QList shortcuts {tag->shortcut()}; m_shortcut->setShortcut(shortcuts); m_removeShortcut->setEnabled(!tag->shortcut().isEmpty()); m_inherit->setChecked(tag->inheritedBySiblings()); } void TagsEditDialog::saveStateTo(State *state) { state->setName(m_stateName->text()); state->setEmblem(m_emblem->icon()); state->setBackgroundColor(m_backgroundColor->color()); state->setBold(m_bold->isChecked()); state->setUnderline(m_underline->isChecked()); state->setItalic(m_italic->isChecked()); state->setStrikeOut(m_strike->isChecked()); state->setTextColor(m_textColor->color()); state->setTextEquivalent(m_textEquivalent->text()); state->setOnAllTextLines(m_onEveryLines->isChecked()); state->setAllowCrossReferences(m_allowCrossRefernce->isChecked()); if (m_font->currentIndex() == 0) state->setFontName(""); else state->setFontName(m_font->currentFont().family()); bool conversionOk; int fontSize = m_fontSize->currentText().toInt(&conversionOk); if (conversionOk) state->setFontSize(fontSize); else state->setFontSize(-1); } void TagsEditDialog::saveTagTo(Tag *tag) { tag->setName(m_tagName->text()); QKeySequence shortcut; if (m_shortcut->shortcut().count() > 0) shortcut = m_shortcut->shortcut()[0]; tag->setShortcut(shortcut); tag->setInheritedBySiblings(m_inherit->isChecked()); } void TagsEditDialog::slotCancel() { // All copies of tag have a shortcut, that is stored as a QAction. // So, shortcuts are duplicated, and if the user press one tag keyboard-shortcut in the main window, there is a conflict. // We then should delete every copies: for (TagCopy::List::iterator tagCopyIt = m_tagCopies.begin(); tagCopyIt != m_tagCopies.end(); ++tagCopyIt) { delete (*tagCopyIt)->newTag; } } void TagsEditDialog::slotOk() { Tag::all.clear(); for (TagCopy::List::iterator tagCopyIt = m_tagCopies.begin(); tagCopyIt != m_tagCopies.end(); ++tagCopyIt) { TagCopy *tagCopy = *tagCopyIt; // Copy changes to the tag and append in the list of tags:: if (tagCopy->oldTag) { tagCopy->newTag->copyTo(tagCopy->oldTag); delete tagCopy->newTag; } Tag *tag = (tagCopy->oldTag ? tagCopy->oldTag : tagCopy->newTag); Tag::all.append(tag); // And change all states: State::List &states = tag->states(); StateCopy::List &stateCopies = tagCopy->stateCopies; states.clear(); for (StateCopy::List::iterator stateCopyIt = stateCopies.begin(); stateCopyIt != stateCopies.end(); ++stateCopyIt) { StateCopy *stateCopy = *stateCopyIt; // Copy changes to the state and append in the list of tags: if (stateCopy->oldState) stateCopy->newState->copyTo(stateCopy->oldState); State *state = (stateCopy->oldState ? stateCopy->oldState : stateCopy->newState); states.append(state); state->setParentTag(tag); } } Tag::saveTags(); // Notify removed states and tags, and then remove them: if (!m_deletedStates.isEmpty()) Global::bnpView->removedStates(m_deletedStates); // Update every note (change colors, size because of font change or added/removed emblems...): Global::bnpView->relayoutAllBaskets(); Global::bnpView->recomputeAllStyles(); } void TagListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { // TagListViewItem* thisItem = qvariant_cast(index.data()); // qDebug() << "Pointer is: " << thisItem << endl; QItemDelegate::paint(painter, option, index); } diff --git a/src/tagsedit.h b/src/tagsedit.h index 41ac487..5e36342 100644 --- a/src/tagsedit.h +++ b/src/tagsedit.h @@ -1,221 +1,208 @@ -/*************************************************************************** - * Copyright (C) 2005 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2005 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef TAGEDIT_H #define TAGEDIT_H #include #include #include #include #include "tag.h" class QCheckBox; class QFontComboBox; class QGroupBox; class QHBoxLayout; class QLabel; class QLineEdit; class QTreeWidget; class QKeyEvent; class QMouseEvent; class KIconButton; class QPushButton; class QKeySequence; class KShortcutWidget; class FontSizeCombo; class KColorCombo2; class StateCopy { public: typedef QList List; explicit StateCopy(State *old = nullptr); ~StateCopy(); State *oldState; State *newState; void copyBack(); }; class TagCopy { public: typedef QList List; explicit TagCopy(Tag *old = nullptr); ~TagCopy(); Tag *oldTag; Tag *newTag; StateCopy::List stateCopies; void copyBack(); bool isMultiState(); }; class TagListViewItem : public QTreeWidgetItem { friend class TagListDelegate; public: TagListViewItem(QTreeWidget *parent, TagCopy *tagCopy); TagListViewItem(QTreeWidgetItem *parent, TagCopy *tagCopy); TagListViewItem(QTreeWidget *parent, QTreeWidgetItem *after, TagCopy *tagCopy); TagListViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, TagCopy *tagCopy); TagListViewItem(QTreeWidget *parent, StateCopy *stateCopy); TagListViewItem(QTreeWidgetItem *parent, StateCopy *stateCopy); TagListViewItem(QTreeWidget *parent, QTreeWidgetItem *after, StateCopy *stateCopy); TagListViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, StateCopy *stateCopy); ~TagListViewItem() override; TagCopy *tagCopy() { return m_tagCopy; } StateCopy *stateCopy() { return m_stateCopy; } bool isEmblemObligatory(); TagListViewItem *lastChild(); TagListViewItem *prevSibling(); TagListViewItem *nextSibling(); TagListViewItem *parent() const; // Reimplemented to cast the return value int width(const QFontMetrics &fontMetrics, const QTreeWidget *listView, int column) const; void setup(); private: TagCopy *m_tagCopy; StateCopy *m_stateCopy; }; Q_DECLARE_METATYPE(TagListViewItem *); class TagListView : public QTreeWidget { Q_OBJECT public: explicit TagListView(QWidget *parent = nullptr); ~TagListView() override; void keyPressEvent(QKeyEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; TagListViewItem *currentItem() const; // Reimplemented to cast the return value TagListViewItem *firstChild() const; // Reimplemented to cast the return value TagListViewItem *lastItem() const; // Reimplemented to cast the return value signals: void deletePressed(); void doubleClickedItem(); }; /** * @author Sébastien Laoût */ class TagsEditDialog : public QDialog { Q_OBJECT public: explicit TagsEditDialog(QWidget *parent = nullptr, State *stateToEdit = nullptr, bool addNewTag = false); ~TagsEditDialog() override; State::List deletedStates() { return m_deletedStates; } State::List addedStates() { return m_addedStates; } TagListViewItem *itemForState(State *state); private slots: void newTag(); void newState(); void moveUp(); void moveDown(); void deleteTag(); void renameIt(); void capturedShortcut(const QKeySequence &shortcut); void removeShortcut(); void removeEmblem(); void modified(); void currentItemChanged(QTreeWidgetItem *item, QTreeWidgetItem *next = 0); void slotCancel(); void slotOk(); void selectUp(); void selectDown(); void selectLeft(); void selectRight(); void resetTreeSizeHint(); private: void loadBlankState(); void loadStateFrom(State *state); void loadTagFrom(Tag *tag); void saveStateTo(State *state); void saveTagTo(Tag *tag); void ensureCurrentItemVisible(); TagListView *m_tags; QPushButton *m_moveUp; QPushButton *m_moveDown; QPushButton *m_deleteTag; QLineEdit *m_tagName; KShortcutWidget *m_shortcut; QPushButton *m_removeShortcut; QCheckBox *m_inherit; QGroupBox *m_tagBox; QHBoxLayout *m_tagBoxLayout; QGroupBox *m_stateBox; QHBoxLayout *m_stateBoxLayout; QLabel *m_stateNameLabel; QLineEdit *m_stateName; KIconButton *m_emblem; QPushButton *m_removeEmblem; QPushButton *m_bold; QPushButton *m_underline; QPushButton *m_italic; QPushButton *m_strike; KColorCombo2 *m_textColor; QFontComboBox *m_font; FontSizeCombo *m_fontSize; KColorCombo2 *m_backgroundColor; QLineEdit *m_textEquivalent; QCheckBox *m_onEveryLines; QCheckBox *m_allowCrossRefernce; TagCopy::List m_tagCopies; State::List m_deletedStates; State::List m_addedStates; bool m_loading; }; class TagListDelegate : public QItemDelegate { Q_OBJECT public: explicit TagListDelegate(QWidget *parent = nullptr) : QItemDelegate(parent) { } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; }; #endif // TAGEDIT_H diff --git a/src/tests/basketviewtest.cpp b/src/tests/basketviewtest.cpp index 9776195..791b2e6 100644 --- a/src/tests/basketviewtest.cpp +++ b/src/tests/basketviewtest.cpp @@ -1,39 +1,25 @@ -/* - * Copyright (C) 2009 by Matt Rogers - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +/** + * SPDX-FileCopyrightText: (C) 2009 Matt Rogers + * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include "basketview.h" #include "note.h" class BasketViewTest : public QObject { Q_OBJECT private Q_SLOTS: void testCreation(); }; QTEST_MAIN(BasketViewTest) void BasketViewTest::testCreation() { } #include "basketviewtest.moc" /* vim: set et sts=4 sw=4 ts=8 tw=0 : */ diff --git a/src/tests/notetest.cpp b/src/tests/notetest.cpp index 88fe4c3..095531e 100644 --- a/src/tests/notetest.cpp +++ b/src/tests/notetest.cpp @@ -1,50 +1,36 @@ -/* - * Copyright (C) 2009 by Matt Rogers - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +/** + * SPDX-FileCopyrightText: (C) 2009 Matt Rogers + * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include "basketview.h" #include "note.h" class NoteTest : public QObject { Q_OBJECT private Q_SLOTS: void testCreation(); }; QTEST_MAIN(NoteTest) void NoteTest::testCreation() { Note *n = new Note(0); QVERIFY(n->basket() == 0); QVERIFY(n->next() == 0); QVERIFY(n->prev() == 0); QVERIFY(n->content() == 0); QCOMPARE(n->x(), 0.0); QCOMPARE(n->y(), 0.0); QCOMPARE(n->width(), Note::GROUP_WIDTH); QCOMPARE(n->height(), Note::MIN_HEIGHT); delete n; } #include "notetest.moc" /* vim: set et sts=4 sw=4 ts=8 tw=0 : */ diff --git a/src/tests/toolstest.cpp b/src/tests/toolstest.cpp index 08cc99f..abbe5a4 100644 --- a/src/tests/toolstest.cpp +++ b/src/tests/toolstest.cpp @@ -1,64 +1,50 @@ -/* - * Copyright (C) 2014 by Gleb Baryshev - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +/** + * SPDX-FileCopyrightText: (C) 2014 Gleb Baryshev + * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include "tools.h" class ToolsTest : public QObject { Q_OBJECT private Q_SLOTS: void testHtmlToText(); private: bool readAll(QString fileName, QString &text); }; QTEST_MAIN(ToolsTest) void ToolsTest::testHtmlToText() { // Test the function on files from htmltotext/ for (int i = 1; i <= 5; i++) { QString html, text; QString basename = QFINDTESTDATA("htmltotext/"); QVERIFY2(QDir(basename).exists(), "Test data file not found"); basename += QString::number(i); if (readAll(basename + ".html", html) && readAll(basename + ".txt", text)) QCOMPARE(Tools::htmlToText(html), text); } } bool ToolsTest::readAll(QString fileName, QString &text) { QFile f(fileName); if (!f.open(QFile::ReadOnly | QFile::Text)) { QWARN(QString("Failed to open data file %1 - skipping").arg(fileName).toUtf8()); return false; } QTextStream filestream(&f); text = filestream.readAll(); return true; } #include "toolstest.moc" diff --git a/src/tools.cpp b/src/tools.cpp index 26ad640..4fced48 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -1,784 +1,771 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "tools.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //For KIO::trash #include #include "config.h" #include "debugwindow.h" // cross reference #include "bnpview.h" #include "global.h" #include "htmlexporter.h" #include "linklabel.h" #include QVector StopWatch::starts; QVector StopWatch::totals; QVector StopWatch::counts; void StopWatch::start(int id) { if (id >= starts.size()) { totals.resize(id + 1); counts.resize(id + 1); for (int i = starts.size(); i <= id; i++) { totals[i] = 0; counts[i] = 0; } starts.resize(id + 1); } starts[id] = QTime::currentTime(); } void StopWatch::check(int id) { if (id >= starts.size()) return; double time = starts[id].msecsTo(QTime::currentTime()) / 1000.0; totals[id] += time; counts[id]++; qDebug() << Q_FUNC_INFO << "Timer_" << id << ": " << time << " s [" << counts[id] << " times, total: " << totals[id] << " s, average: " << totals[id] / counts[id] << " s]" << endl; } /** @namespace HTM * @brief HTML tags constants */ namespace HTM { static const char *BR = "
    "; static const char *PAR = "

    "; static const char *_PAR = "

    "; // Styles static const char *FONT_FAMILY = "font-family: %1; "; static const char *FONT_STYLE = "font-style: %1; "; static const char *TEXT_DECORATION = "text-decoration: %1; "; static const char *ITALIC = "italic"; static const char *UNDERLINE = "underline"; static const char *LINE_THROUGH = "line-through"; // static const char* FONT_WEIGHT = "font-weight: %1; "; static const char *FONT_SIZE = "font-size: %1pt; "; static const char *COLOR = "color: %1; "; } QString Tools::textToHTML(const QString &text) { if (text.isEmpty()) return "

    "; if (/*text.isEmpty() ||*/ text == " " || text == " ") return "

     

    "; // convertFromPlainText() replace "\n\n" by "

    \n

    ": we don't want that QString htmlString = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal); return htmlString.replace("

    \n", "
    \n
    \n").replace("\n

    ", "\n"); // Don't replace first and last tags } QString Tools::textToHTMLWithoutP(const QString &text) { // textToHTML(text) return "

    HTMLizedText

    ". We remove the strating "

    " and ending

    " QString HTMLizedText = textToHTML(text); return HTMLizedText.mid(3, HTMLizedText.length() - 3 - 4); } QString Tools::htmlToParagraph(const QString &html) { QString result = html; bool startedBySpan = false; // Remove the start tag, all the and the start // Because can contain style="..." parameter, we transform it to int pos = result.indexOf("\n", each tag can be separated by space characters (%s) // "

    " can be omitted (eg. if the HTML doesn't contain paragraph but tables), as well as "" (optional) pos = result.indexOf(QRegExp("(?:(?:

    [\\s\\n\\r\\t]*)*[\\s\\n\\r\\t]*)*", Qt::CaseInsensitive)); if (pos != -1) result = result.left(pos); if (startedBySpan) result += "
    "; return result; } // The following is adapted from KStringHanlder::tagURLs // The adaptation lies in the change to urlEx // Thanks to Richard Heck QString Tools::tagURLs(const QString &text) { QRegExp urlEx(""); QString richText(text); int urlPos = 0; int urlLen; if ((urlPos = urlEx.indexIn(richText, urlPos)) >= 0) urlPos += urlEx.matchedLength(); else urlPos = 0; urlEx.setPattern("(www\\.(?!\\.)|(fish|(f|ht)tp(|s))://)[\\d\\w\\./,:_~\\?=&;#@\\-\\+\\%\\$]+[\\d\\w/]"); while ((urlPos = urlEx.indexIn(richText, urlPos)) >= 0) { urlLen = urlEx.matchedLength(); // if this match is already a link don't convert it. if (richText.mid(urlPos - 6, 6) == "href=\"") { urlPos += urlLen; continue; } QString href = richText.mid(urlPos, urlLen); // we handle basket links separately... if (href.contains("basket://")) { urlPos += urlLen; continue; } // Qt doesn't support (?<=pattern) so we do it here if ((urlPos > 0) && richText[urlPos - 1].isLetterOrNumber()) { urlPos++; continue; } // Don't use QString::arg since %01, %20, etc could be in the string QString anchor = "" + href + ""; richText.replace(urlPos, urlLen, anchor); urlPos += anchor.length(); } return richText; } QString Tools::tagCrossReferences(const QString &text, bool userLink, HTMLExporter *exporter) { QString richText(text); int urlPos = 0; int urlLen; QRegExp urlEx("\\[\\[(.+)\\]\\]"); urlEx.setMinimal(true); while ((urlPos = urlEx.indexIn(richText, urlPos)) >= 0) { urlLen = urlEx.matchedLength(); QString href = urlEx.cap(1); QStringList hrefParts = href.split('|'); QString anchor; if (exporter) // if we're exporting this basket to html. anchor = crossReferenceForHtml(hrefParts, exporter); else if (userLink) // the link is manually created (ie [[/top level/sub]] ) anchor = crossReferenceForConversion(hrefParts); else // otherwise it's a standard link (ie. [[basket://basket107]] ) anchor = crossReferenceForBasket(hrefParts); richText.replace(urlPos, urlLen, anchor); urlPos += anchor.length(); } return richText; } QString Tools::crossReferenceForBasket(QStringList linkParts) { QString basketLink = linkParts.first(); QString title; bool linkIsEmpty = false; if (basketLink == "basket://" || basketLink.isEmpty()) linkIsEmpty = true; title = linkParts.last().trimmed(); QString css = LinkLook::crossReferenceLook->toCSS("cross_reference", QColor()); QString classes = "cross_reference"; classes += (linkIsEmpty ? " xref_empty" : ""); css += (linkIsEmpty ? " a.xref_empty { display: block; width: 100%; text-decoration: underline; color: #CC2200; }" " a:hover.xref_empty { color: #A55858; }" : ""); QString anchor = "" + QUrl::fromPercentEncoding(title.toUtf8()) + ""; return anchor; } QString Tools::crossReferenceForHtml(QStringList linkParts, HTMLExporter *exporter) { QString basketLink = linkParts.first(); QString title; bool linkIsEmpty = false; if (basketLink == "basket://" || basketLink.isEmpty()) linkIsEmpty = true; title = linkParts.last().trimmed(); QString url; if (basketLink.startsWith(QLatin1String("basket://"))) url = basketLink.mid(9, basketLink.length() - 9); BasketScene *basket = Global::bnpView->basketForFolderName(url); // remove the trailing slash. url = url.left(url.length() - 1); // if the basket we're trying to link to is the basket that was exported then // we have to use a special way to refer to it for the links. if (basket == exporter->exportedBasket) url = "../../" + exporter->fileName; else { // if we're in the exported basket then the links have to include // the sub directories. if (exporter->currentBasket == exporter->exportedBasket) url.prepend(exporter->basketsFolderName); if (!url.isEmpty()) url.append(".html"); } QString classes = "cross_reference"; classes += (linkIsEmpty ? " xref_empty" : ""); QString css = (linkIsEmpty ? " a.xref_empty { display: block; width: 100%; text-decoration: underline; color: #CC2200; }" " a:hover.xref_empty { color: #A55858; }" : ""); QString anchor = "" + QUrl::fromPercentEncoding(title.toUtf8()) + ""; return anchor; } QString Tools::crossReferenceForConversion(QStringList linkParts) { QString basketLink = linkParts.first(); QString title; if (basketLink.startsWith(QLatin1String("basket://"))) return QString("[[%1|%2]]").arg(basketLink, linkParts.last()); if (basketLink.endsWith('/')) basketLink = basketLink.left(basketLink.length() - 1); QStringList pages = basketLink.split('/'); if (linkParts.count() <= 1) title = pages.last(); else title = linkParts.last().trimmed(); QString url = Global::bnpView->folderFromBasketNameLink(pages); url.prepend("basket://"); QString anchor; // if we don't change the link return it back exactly // as it came in because it may not be a link. if (url == "basket://" || url.isEmpty()) { QString returnValue = ""; foreach (QString s, linkParts) returnValue.append(s); anchor = returnValue.prepend("[[").append("]]"); } else anchor = QString("[[%1|%2]]").arg(url, title); return anchor; } QString Tools::htmlToText(const QString &html) { QString text = htmlToParagraph(html); text.remove('\n'); text.replace("", "\n"); text.replace("", "\n"); text.replace("", "\n"); text.replace("", "\n"); text.replace("", "\n"); text.replace("", "\n"); text.replace("
  • ", "\n"); text.replace("", "\n"); text.replace("", "\n"); text.replace("
    ", " "); text.replace("", "\n"); text.replace("", "\n"); text.replace("", "\n"); text.replace("", "\n"); text.replace("", " "); text.replace("", " "); text.replace("
    ", "\n"); text.replace("
    ", "\n"); text.replace("

    ", "\n"); // FIXME: Format tags better, if possible // TODO: Replace é and co. by their equivalent! // To manage tags: int pos = 0; int pos2; QString tag, tag3; // To manage lists: int deep = 0; // The deep of the current line in imbriqued lists QList ul; // true if current list is a
      one, false if it's an
        one QList lines; // The line number if it is an
          list // We're removing every other tags, or replace them in the case of li: while ((pos = text.indexOf("<"), pos) != -1) { // What is the current tag? tag = text.mid(pos + 1, 2); tag3 = text.mid(pos + 1, 3); // Lists work: if (tag == "ul") { deep++; ul.push_back(true); lines.push_back(-1); } else if (tag == "ol") { deep++; ul.push_back(false); lines.push_back(0); } else if (tag3 == "/ul" || tag3 == "/ol") { deep--; ul.pop_back(); lines.pop_back(); } // Where the tag closes? pos2 = text.indexOf(">"); if (pos2 != -1) { // Remove the tag: text.remove(pos, pos2 - pos + 1); // And replace li with "* ", "x. "... without forbidding to indent that: if (tag == "li") { // How many spaces before the line (indentation): QString spaces = ""; for (int i = 1; i < deep; i++) spaces += " "; // The bullet or number of the line: QString bullet = "* "; if (ul.back() == false) { lines.push_back(lines.back() + 1); lines.pop_back(); bullet = QString::number(lines.back()) + ". "; } // Insertion: text.insert(pos, spaces + bullet); } if ((tag3 == "/ul" || tag3 == "/ol") && deep == 0) text.insert(pos, "\n"); // Empty line before and after a set of lists } ++pos; } text.replace(">", ">"); text.replace("<", "<"); text.replace(""", "\""); text.replace(" ", " "); text.replace("&", "&"); // CONVERT IN LAST!! // HtmlContent produces "\n" for empty note if (text == "\n") text = ""; return text; } QString Tools::textDocumentToMinimalHTML(QTextDocument *document) { QString result = "\n" "\n"; QFont defaultFont; int fragCount, blockCount = 0; bool leadingBrNeeded = false; for (QTextBlock blockIt = document->begin(); blockIt != document->end(); blockIt = blockIt.next(), ++blockCount) { result += HTM::PAR; // Prepare to detect empty blocks fragCount = 0; for (QTextBlock::iterator subIt = blockIt.begin(); !(subIt.atEnd()); ++subIt, ++fragCount) { QTextFragment currentFragment = subIt.fragment(); if (currentFragment.isValid()) { // Dealing with need to add leading linebreak (see later for // further notes) if (leadingBrNeeded) { result += HTM::BR; leadingBrNeeded = false; } QTextCharFormat charFmt = currentFragment.charFormat(); const QColor &textColor = charFmt.foreground().color(); bool isTextBlack = (textColor == QColor() || textColor == QColor(Qt::black)); if (charFmt.font() == defaultFont && isTextBlack) { result += currentFragment.text().toHtmlEscaped(); continue; } // If we use charFmt.fontWeight, setting a tag overrides it and all characters become non-bold. // So we use instead bool bold = (charFmt.fontWeight() >= QFont::Bold); if (bold) result += ""; // Compose style string (font and color) result += "= QFont::Bold) ? QFont::Bold : QFont::Normal; result += QString(HTM::FONT_WEIGHT).arg(weight); }*/ if (charFmt.fontPointSize() != defaultFont.pointSize() && charFmt.fontPointSize() != 0) result += QString(HTM::FONT_SIZE).arg(charFmt.fontPointSize()); if (!isTextBlack) result += QString(HTM::COLOR).arg(textColor.name()); result += "\">" + currentFragment.text().toHtmlEscaped() + ""; if (bold) result += ""; } } // Detecting empty blocks (Qt4 fails to generate a fragment from an empty line) // Inserting a linebreak directly here seems to cause the renderer to render // two breaks, so have to append it to the contents of the previous paragraph... if (!fragCount) { // If the first fragment is an empty fragment, the linebreak must be // added to the next fragment otherwise you get the above double breaks if (!blockCount) leadingBrNeeded = true; // Deal with the problem only when the last block is not affected, // otherwise you get double breaks again... Blocks counted from 0 else if (blockCount != (document->blockCount() - 1)) { result.chop(7); result = result + HTM::BR + HTM::_PAR + HTM::PAR; } } result += HTM::_PAR; } result += ""; return result; } QString Tools::cssFontDefinition(const QFont &font, bool onlyFontFamily) { // The font definition: QString definition = QString(font.italic() ? "italic " : "") + QString(font.bold() ? "bold " : "") + QString::number(QFontInfo(font).pixelSize()) + "px "; // Then, try to match the font name with a standard CSS font family: QString genericFont = ""; if (definition.contains("serif", Qt::CaseInsensitive) || definition.contains("roman", Qt::CaseInsensitive)) genericFont = "serif"; // No "else if" because "sans serif" must be counted as "sans". So, the order between "serif" and "sans" is important if (definition.contains("sans", Qt::CaseInsensitive) || definition.contains("arial", Qt::CaseInsensitive) || definition.contains("helvetica", Qt::CaseInsensitive)) genericFont = "sans-serif"; if (definition.contains("mono", Qt::CaseInsensitive) || definition.contains("courier", Qt::CaseInsensitive) || definition.contains("typewriter", Qt::CaseInsensitive) || definition.contains("console", Qt::CaseInsensitive) || definition.contains("terminal", Qt::CaseInsensitive) || definition.contains("news", Qt::CaseInsensitive)) genericFont = "monospace"; // Eventually add the generic font family to the definition: QString fontDefinition = "\"" + font.family() + "\""; if (!genericFont.isEmpty()) fontDefinition += ", " + genericFont; if (onlyFontFamily) return fontDefinition; return definition + fontDefinition; } QString Tools::stripEndWhiteSpaces(const QString &string) { uint length = string.length(); uint i; for (i = length; i > 0; --i) if (!string[i - 1].isSpace()) break; if (i == 0) return ""; else return string.left(i); } bool Tools::isWebColor(const QColor &color) { int r = color.red(); // The 216 web colors are those colors whose RGB (Red, Green, Blue) int g = color.green(); // values are all in the set (0, 51, 102, 153, 204, 255). int b = color.blue(); return ((r == 0 || r == 51 || r == 102 || r == 153 || r == 204 || r == 255) && (g == 0 || g == 51 || g == 102 || g == 153 || g == 204 || g == 255) && (b == 0 || b == 51 || b == 102 || b == 153 || b == 204 || b == 255)); } QColor Tools::mixColor(const QColor &color1, const QColor &color2, const float ratio) { QColor mixedColor; mixedColor.setRgb((color1.red() * ratio + color2.red()) / (1 + ratio), (color1.green() * ratio + color2.green()) / (1 + ratio), (color1.blue() * ratio + color2.blue()) / (1 + ratio)); return mixedColor; } bool Tools::tooDark(const QColor &color) { return color.value() < 175; } // TODO: Use it for all indentPixmap() QPixmap Tools::normalizePixmap(const QPixmap &pixmap, int width, int height) { if (height <= 0) height = width; if (pixmap.isNull() || (pixmap.width() == width && pixmap.height() == height)) return pixmap; return pixmap; } QPixmap Tools::indentPixmap(const QPixmap &source, int depth, int deltaX) { // Verify if it is possible: if (depth <= 0 || source.isNull()) return source; // Compute the number of pixels to indent: if (deltaX <= 0) deltaX = 2 * source.width() / 3; int indent = depth * deltaX; // Create the images: QImage resultImage(indent + source.width(), source.height(), QImage::Format_ARGB32); resultImage.setColorCount(32); QImage sourceImage = source.toImage(); // Clear the indent part (the left part) by making it fully transparent: uint *p; for (int row = 0; row < resultImage.height(); ++row) { for (int column = 0; column < resultImage.width(); ++column) { p = (uint *)resultImage.scanLine(row) + column; *p = 0; // qRgba(0, 0, 0, 0) } } // Copy the source image byte per byte to the right part: uint *q; for (int row = 0; row < sourceImage.height(); ++row) { for (int column = 0; column < sourceImage.width(); ++column) { p = (uint *)resultImage.scanLine(row) + indent + column; q = (uint *)sourceImage.scanLine(row) + column; *p = *q; } } // And return the result: QPixmap result = QPixmap::fromImage(resultImage); return result; } void Tools::deleteRecursively(const QString &folderOrFile) { if (folderOrFile.isEmpty()) return; QFileInfo fileInfo(folderOrFile); if (fileInfo.isDir()) { // Delete the child files: QDir dir(folderOrFile, QString(), QDir::Name | QDir::IgnoreCase, QDir::TypeMask | QDir::Hidden); QStringList list = dir.entryList(); for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) if (*it != "." && *it != "..") deleteRecursively(folderOrFile + '/' + *it); // And then delete the folder: dir.rmdir(folderOrFile); } else // Delete the file: QFile::remove(folderOrFile); } void Tools::deleteMetadataRecursively(const QString &folderOrFile) { QFileInfo fileInfo(folderOrFile); if (fileInfo.isDir()) { // Delete Metadata of the child files: QDir dir(folderOrFile, QString(), QDir::Name | QDir::IgnoreCase, QDir::TypeMask | QDir::Hidden); QStringList list = dir.entryList(); for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) if (*it != "." && *it != "..") deleteMetadataRecursively(folderOrFile + '/' + *it); } } void Tools::trashRecursively(const QString &folderOrFile) { if (folderOrFile.isEmpty()) return; KIO::trash(QUrl::fromLocalFile(folderOrFile), KIO::HideProgressInfo); } QString Tools::fileNameForNewFile(const QString &wantedName, const QString &destFolder) { QString fileName = wantedName; QString fullName = destFolder + fileName; QString extension = ""; int number = 2; QDir dir; // First check if the file do not exists yet (simplier and more often case) dir = QDir(fullName); if (!dir.exists(fullName)) return fileName; // Find the file extension, if it exists : Split fileName in fileName and extension // Example : fileName == "note5-3.txt" => fileName = "note5-3" and extension = ".txt" int extIndex = fileName.lastIndexOf('.'); if (extIndex != -1 && extIndex != int(fileName.length() - 1)) { // Extension found and fileName do not ends with '.' ! extension = fileName.mid(extIndex); fileName.truncate(extIndex); } // else fileName = fileName and extension = "" // Find the file number, if it exists : Split fileName in fileName and number // Example : fileName == "note5-3" => fileName = "note5" and number = 3 int extNumber = fileName.lastIndexOf('-'); if (extNumber != -1 && extNumber != int(fileName.length() - 1)) { // Number found and fileName do not ends with '-' ! bool isANumber; int theNumber = fileName.mid(extNumber + 1).toInt(&isANumber); if (isANumber) { number = theNumber; fileName.truncate(extNumber); } // else : } // else fileName = fileName and number = 2 (because if the file already exists, the genereated name is at last the 2nd) QString finalName; for (/*int number = 2*/;; ++number) { // TODO: FIXME: If overflow ??? finalName = fileName + '-' + QString::number(number) + extension; fullName = destFolder + finalName; dir = QDir(fullName); if (!dir.exists(fullName)) break; } return finalName; } qint64 Tools::computeSizeRecursively(const QString &path) { qint64 result = 0; QFileInfo file(path); result += file.size(); if (file.isDir()) { QFileInfoList children = QDir(path).entryInfoList(QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot | QDir::Hidden); foreach (const QFileInfo &child, children) result += computeSizeRecursively(child.absoluteFilePath()); } return result; } // TODO: Move it from NoteFactory /*QString NoteFactory::iconForURL(const QUrl &url) { QString icon = KMimeType::iconNameForUrl(url.url()); if ( url.scheme() == "mailto" ) icon = "message"; return icon; }*/ bool Tools::isAFileCut(const QMimeData *source) { if (source->hasFormat("application/x-kde-cutselection")) { QByteArray array = source->data("application/x-kde-cutselection"); return !array.isEmpty() && QByteArray(array.data(), array.size() + 1).at(0) == '1'; } else return false; } void Tools::printChildren(QObject *parent) { const QObjectList objs = parent->children(); QObject *obj; for (int i = 0; i < objs.size(); i++) { obj = objs.at(i); qDebug() << Q_FUNC_INFO << obj->metaObject()->className() << ": " << obj->objectName() << endl; } } QString Tools::makeStandardCaption(const QString &userCaption) { QString caption = QGuiApplication::applicationDisplayName(); if (!userCaption.isEmpty()) return userCaption + i18nc("Document/application separator in titlebar", " – ") + caption; else return caption; } QByteArray Tools::systemCodeset() { QByteArray codeset; #if HAVE_LANGINFO_H // Qt since 4.2 always returns 'System' as codecForLocale and libraries like for example // KEncodingFileDialog expects real encoding name. So on systems that have langinfo.h use // nl_langinfo instead, just like Qt compiled without iconv does. Windows already has its own // workaround codeset = nl_langinfo(CODESET); if ((codeset == "ANSI_X3.4-1968") || (codeset == "US-ASCII")) { // means ascii, "C"; QTextCodec doesn't know, so avoid warning codeset = "ISO-8859-1"; } #endif return codeset; } diff --git a/src/tools.h b/src/tools.h index bf533df..ebbf073 100644 --- a/src/tools.h +++ b/src/tools.h @@ -1,127 +1,114 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef TOOLS_H #define TOOLS_H #include class QColor; class QFont; class QMimeData; class QObject; class QPixmap; class QString; class QStringList; class QTime; class QTextDocument; class HTMLExporter; class StopWatch { public: static void start(int id); static void check(int id); private: static QVector starts; static QVector totals; static QVector counts; }; /** Some useful functions for that application. * @author Sébastien Laoût */ namespace Tools { // Text <-> HTML Conversions and Tools: QString textToHTML(const QString &text); QString textToHTMLWithoutP(const QString &text); QString htmlToParagraph(const QString &html); QString htmlToText(const QString &html); QString textDocumentToMinimalHTML(QTextDocument *document); //!< Avoid unneeded spans and style attributes QString tagURLs(const QString &test); QString cssFontDefinition(const QFont &font, bool onlyFontFamily = false); // Cross Reference tools: QString tagCrossReferences(const QString &text, bool userLink = false, HTMLExporter *exporter = 0); // private functions: QString crossReferenceForBasket(QStringList linkParts); QString crossReferenceForHtml(QStringList linkParts, HTMLExporter *exporter); QString crossReferenceForConversion(QStringList linkParts); // String Manipulations: QString stripEndWhiteSpaces(const QString &string); QString makeStandardCaption(const QString &userCaption); //!< Replacement for KDialog::makeStandardCaption // Pixmap Manipulations: /** @Return true if it is a Web color */ bool isWebColor(const QColor &color); /** @Return a color that is 50% of @p color1 and 50% of @p color2. */ QColor mixColor(const QColor &color1, const QColor &color2, const float ratio = 1); /** @Return true if the color is too dark to be darkened one more time. */ bool tooDark(const QColor &color); /** Make sure the @p pixmap is of the size (@p width, @p height) and @return a pixmap of this size. * If @p height <= 0, then width will be used to make the picture square. */ QPixmap normalizePixmap(const QPixmap &pixmap, int width, int height = 0); /** @Return the pixmap @p source with depth*deltaX transparent pixels added to the left.\n * If @p deltaX is <= 0, then an indent delta is computed depending on the @p source width. */ QPixmap indentPixmap(const QPixmap &source, int depth, int deltaX = 0); // File and Folder Manipulations: /** Delete the folder @p folderOrFile recursively (to remove sub-folders and child files too). */ void deleteRecursively(const QString &folderOrFile); /** Trash the folder @p folderOrFile recursively (to move sub-folders and child files to the Trash, too). */ void trashRecursively(const QString &folderOrFile); /** Delete the metadata of file or folder @p folderOrFile from Nepomuk, recursively. */ void deleteMetadataRecursively(const QString &folderOrFile); /** @Return a new filename that doesn't already exist in @p destFolder. * If @p wantedName already exist in @p destFolder, a dash and a number will be added before the extension. * Id there were already such a number in @p wantedName, it is incremented until a free filename is found. */ QString fileNameForNewFile(const QString &wantedName, const QString &destFolder); //! @returns Total size in bytes of all files and subdirectories qint64 computeSizeRecursively(const QString &path); // Other: // void iconForURL(const QUrl &url); /** @Return true if the source is from a file cutting in Konqueror. * @Return false if it was just a copy or if it was a drag. */ bool isAFileCut(const QMimeData *source); /// Implementation of system encoding detection from KDE 4 QByteArray systemCodeset(); // Debug void printChildren(QObject *parent); } #endif // TOOLS_H diff --git a/src/transparentwidget.cpp b/src/transparentwidget.cpp index 923c567..7fbae72 100644 --- a/src/transparentwidget.cpp +++ b/src/transparentwidget.cpp @@ -1,81 +1,68 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * -+ ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "transparentwidget.h" #include "basketscene.h" #include "basketview.h" #include #include #include #include /** Class TransparentWidget */ // TODO: Why was Qt::WNoAutoErase used here? TransparentWidget::TransparentWidget(BasketScene *basket) : QWidget(basket->graphicsView()->viewport()) , m_basket(basket) { setFocusPolicy(Qt::NoFocus); setMouseTracking(true); // To receive mouseMoveEvents basket->graphicsView()->viewport()->installEventFilter(this); } /*void TransparentWidget::reparent(QWidget *parent, Qt::WFlags f, const QPoint &p, bool showIt) { QWidget::reparent(parent, Qt::WNoAutoErase, p, showIt); }*/ void TransparentWidget::setPosition(int x, int y) { m_x = x; m_y = y; } void TransparentWidget::paintEvent(QPaintEvent *event) { QWidget::paintEvent(event); QPainter painter(this); // painter.save(); painter.translate(-m_x, -m_y); // m_basket->drawContents(&painter, m_x, m_y, width(), height()); // painter.restore(); // painter.setPen(Qt::blue); // painter.drawRect(0, 0, width(), height()); } void TransparentWidget::mouseMoveEvent(QMouseEvent *event) { // QMouseEvent *translated = new QMouseEvent(QEvent::MouseMove, event->pos() + QPoint(m_x, m_y), event->button(), event->buttons(), event->modifiers()); // m_basket->contentsMouseMoveEvent(translated); // delete translated; } bool TransparentWidget::eventFilter(QObject * /*object*/, QEvent *event) { // If the parent basket viewport has changed, we should change too: if (event->type() == QEvent::Paint) update(); return false; // Event not consumed, in every cases (because it's only a notification)! } diff --git a/src/transparentwidget.h b/src/transparentwidget.h index 7fcbcdd..0c5abb6 100644 --- a/src/transparentwidget.h +++ b/src/transparentwidget.h @@ -1,48 +1,35 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef TRANSPARENTWIDGET_H #define TRANSPARENTWIDGET_H #include class BasketScene; class QPaintEvent; class QMouseEvent; class QObject; class TransparentWidget : public QWidget { Q_OBJECT public: explicit TransparentWidget(BasketScene *basket); void setPosition(int x, int y); // void reparent(QWidget *parent, Qt::WFlags f, const QPoint &p, bool showIt = FALSE); protected: void paintEvent(QPaintEvent *) override; void mouseMoveEvent(QMouseEvent *event) override; bool eventFilter(QObject *object, QEvent *event) override; private: BasketScene *m_basket; int m_x; int m_y; }; #endif // TRANSPARENTWIDGET_H diff --git a/src/variouswidgets.cpp b/src/variouswidgets.cpp index fb9c698..6b9f5c6 100644 --- a/src/variouswidgets.cpp +++ b/src/variouswidgets.cpp @@ -1,423 +1,410 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "variouswidgets.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** class RunCommandRequester: */ RunCommandRequester::RunCommandRequester(const QString &runCommand, const QString &message, QWidget *parent) : QWidget(parent) { m_message = message; QHBoxLayout *layout = new QHBoxLayout(this); m_runCommand = new QLineEdit(runCommand, this); QPushButton *pb = new QPushButton(/*"C&hoose..."*/ i18n("..."), this); pb->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); layout->addWidget(m_runCommand); layout->addWidget(pb); connect(pb, SIGNAL(clicked()), this, SLOT(slotSelCommand())); } RunCommandRequester::~RunCommandRequester() { } void RunCommandRequester::slotSelCommand() { QPointer dlg = new KOpenWithDialog(QList(), m_message, m_runCommand->text(), this); dlg->exec(); if (!dlg->text().isEmpty()) m_runCommand->setText(dlg->text()); } QString RunCommandRequester::runCommand() { return m_runCommand->text(); } void RunCommandRequester::setRunCommand(const QString &runCommand) { m_runCommand->setText(runCommand); } /** class IconSizeCombo: */ IconSizeCombo::IconSizeCombo(QWidget *parent) : KComboBox(parent) { addItem(i18n("16 by 16 pixels")); addItem(i18n("22 by 22 pixels")); addItem(i18n("32 by 32 pixels")); addItem(i18n("48 by 48 pixels")); addItem(i18n("64 by 64 pixels")); addItem(i18n("128 by 128 pixels")); setCurrentIndex(2); } IconSizeCombo::~IconSizeCombo() { } int IconSizeCombo::iconSize() { switch (currentIndex()) { default: case 0: return 16; case 1: return 22; case 2: return 32; case 3: return 48; case 4: return 64; case 5: return 128; } } void IconSizeCombo::setSize(int size) { switch (size) { default: case 16: setCurrentIndex(0); break; case 22: setCurrentIndex(1); break; case 32: setCurrentIndex(2); break; case 48: setCurrentIndex(3); break; case 64: setCurrentIndex(4); break; case 128: setCurrentIndex(5); break; } } /** class ViewSizeDialog: */ ViewSizeDialog::ViewSizeDialog(QWidget *parent, int w, int h) : QDialog(parent) { QLabel *label = new QLabel(i18n("Resize the window to select the image size\n" "and close it or press Escape to accept changes."), this); label->move(8, 8); label->setFixedSize(label->sizeHint()); // setSizeGripEnabled(true) doesn't work (the grip stay at the same place), so we emulate it: m_sizeGrip = new QSizeGrip(this); m_sizeGrip->setFixedSize(m_sizeGrip->sizeHint()); setGeometry(x(), y(), w, h); } ViewSizeDialog::~ViewSizeDialog() { } void ViewSizeDialog::resizeEvent(QResizeEvent *) { setWindowTitle(i18n("%1 by %2 pixels", QString::number(width()), QString::number(height()))); m_sizeGrip->move(width() - m_sizeGrip->width(), height() - m_sizeGrip->height()); } /** class HelpLabel: */ HelpLabel::HelpLabel(const QString &text, const QString &message, QWidget *parent) : KUrlLabel(parent) , m_message(message) { setText(text); setWhatsThis(m_message); connect(this, SIGNAL(leftClickedUrl()), this, SLOT(display())); } HelpLabel::~HelpLabel() { } void HelpLabel::display() { QWhatsThis::showText(mapToGlobal(QPoint(width() / 2, height())), m_message); } /** class IconSizeDialog: */ class UndraggableKIconView : public QListWidget { public: UndraggableKIconView(QWidget *parent = 0) : QListWidget(parent) { this->setViewMode(QListView::IconMode); this->setMovement(QListView::Static); this->setSelectionMode(QAbstractItemView::SingleSelection); this->setWrapping(false); } QDrag *dragObject() { return 0; } }; IconSizeDialog::IconSizeDialog(const QString &caption, const QString &message, const QString &icon, int iconSize, QWidget *parent) : QDialog(parent) { // QDialog options setWindowTitle(caption); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); setModal(true); QWidget *page = new QWidget(this); QVBoxLayout *topLayout = new QVBoxLayout(page); QLabel *label = new QLabel(message, page); topLayout->addWidget(label); QListWidget *iconView = new UndraggableKIconView(page); m_size16 = new QListWidgetItem(DesktopIcon(icon, 16), i18n("16 by 16 pixels"), iconView); m_size22 = new QListWidgetItem(DesktopIcon(icon, 22), i18n("22 by 22 pixels"), iconView); m_size32 = new QListWidgetItem(DesktopIcon(icon, 32), i18n("32 by 32 pixels"), iconView); m_size48 = new QListWidgetItem(DesktopIcon(icon, 48), i18n("48 by 48 pixels"), iconView); m_size64 = new QListWidgetItem(DesktopIcon(icon, 64), i18n("64 by 64 pixels"), iconView); m_size128 = new QListWidgetItem(DesktopIcon(icon, 128), i18n("128 by 128 pixels"), iconView); iconView->setIconSize(QSize(128, 128)); iconView->setMinimumSize(QSize(128 * 6 + (6 + 2) * iconView->spacing() + 20, m_size128->sizeHint().height() + 2 * iconView->spacing() + 20)); topLayout->addWidget(iconView); switch (iconSize) { case 16: m_size16->setSelected(true); m_iconSize = 16; break; case 22: m_size22->setSelected(true); m_iconSize = 22; break; default: case 32: m_size32->setSelected(true); m_iconSize = 32; break; case 48: m_size48->setSelected(true); m_iconSize = 48; break; case 64: m_size64->setSelected(true); m_iconSize = 64; break; case 128: m_size128->setSelected(true); m_iconSize = 128; break; } connect(iconView, SIGNAL(executed(QListWidgetItem *)), this, SLOT(choose(QListWidgetItem *))); connect(iconView, SIGNAL(itemActivated(QListWidgetItem *)), this, SLOT(choose(QListWidgetItem *))); connect(iconView, SIGNAL(itemSelectionChanged()), this, SLOT(slotSelectionChanged())); mainLayout->addWidget(page); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); connect(buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), SLOT(slotCancel())); } IconSizeDialog::~IconSizeDialog() { } void IconSizeDialog::slotSelectionChanged() { // Change m_iconSize to the new selected one: if (m_size16->isSelected()) { m_iconSize = 16; return; } if (m_size22->isSelected()) { m_iconSize = 22; return; } if (m_size32->isSelected()) { m_iconSize = 32; return; } if (m_size48->isSelected()) { m_iconSize = 48; return; } if (m_size64->isSelected()) { m_iconSize = 64; return; } if (m_size128->isSelected()) { m_iconSize = 128; return; } // But if user unselected the item (by eg. right clicking a free space), reselect the last one: switch (m_iconSize) { case 16: m_size16->setSelected(true); m_iconSize = 16; break; case 22: m_size22->setSelected(true); m_iconSize = 22; break; default: case 32: m_size32->setSelected(true); m_iconSize = 32; break; case 48: m_size48->setSelected(true); m_iconSize = 48; break; case 64: m_size64->setSelected(true); m_iconSize = 64; break; case 128: m_size128->setSelected(true); m_iconSize = 128; break; } } void IconSizeDialog::choose(QListWidgetItem *) { okButton->animateClick(); } void IconSizeDialog::slotCancel() { m_iconSize = -1; } /** class FontSizeCombo: */ FontSizeCombo::FontSizeCombo(bool rw, bool withDefault, QWidget *parent) : KComboBox(rw, parent) , m_withDefault(withDefault) { if (m_withDefault) addItem(i18n("(Default)")); QFontDatabase fontDB; QList sizes = fontDB.standardSizes(); for (QList::Iterator it = sizes.begin(); it != sizes.end(); ++it) addItem(QString::number(*it)); // connect( this, SIGNAL(activated(const QString&)), this, SLOT(textChangedInCombo(const QString&)) ); connect(this, SIGNAL(editTextChanged(const QString &)), this, SLOT(textChangedInCombo(const QString &))); // TODO: 01617 void KFontSizeAction::setFontSize( int size ) } FontSizeCombo::~FontSizeCombo() { } void FontSizeCombo::textChangedInCombo(const QString &text) { bool ok = false; int size = text.toInt(&ok); if (ok) emit sizeChanged(size); } void FontSizeCombo::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) emit escapePressed(); else if (event->key() == Qt::Key_Return) emit returnPressed2(); else KComboBox::keyPressEvent(event); } void FontSizeCombo::setFontSize(qreal size) { setItemText(currentIndex(), QString::number(size)); // TODO: SEE KFontSizeAction::setFontSize( int size ) !!! for a more complete method! } qreal FontSizeCombo::fontSize() { bool ok = false; int size = currentText().toInt(&ok); if (ok) return size; size = currentText().toInt(&ok); if (ok) return size; return font().pointSize(); } diff --git a/src/variouswidgets.h b/src/variouswidgets.h index 581bd22..6a83cf6 100644 --- a/src/variouswidgets.h +++ b/src/variouswidgets.h @@ -1,166 +1,153 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef VARIOUSWIDGETS_H #define VARIOUSWIDGETS_H #include #include #include #include class QLineEdit; class QListWidgetItem; class QResizeEvent; class QString; class QKeyEvent; /** A widget to select a command to run, * with a QLineEdit and a QPushButton. * @author Sébastien Laoût */ class RunCommandRequester : public QWidget { Q_OBJECT public: RunCommandRequester(const QString &runCommand, const QString &message, QWidget *parent = nullptr); ~RunCommandRequester() override; QString runCommand(); void setRunCommand(const QString &runCommand); QLineEdit *lineEdit() { return m_runCommand; } private slots: void slotSelCommand(); private: QLineEdit *m_runCommand; QString m_message; }; /** KComboBox to ask icon size * @author Sébastien Laoût */ class IconSizeCombo : public KComboBox { Q_OBJECT public: explicit IconSizeCombo(QWidget *parent = nullptr); ~IconSizeCombo() override; int iconSize(); void setSize(int size); }; /** A window that the user resize to graphically choose a new image size * TODO: Create a SizePushButton or even SizeWidget * @author Sébastien Laoût */ class ViewSizeDialog : public QDialog { Q_OBJECT public: ViewSizeDialog(QWidget *parent, int w, int h); ~ViewSizeDialog() override; private: void resizeEvent(QResizeEvent *) override; QWidget *m_sizeGrip; }; /** A label displaying a link that, once clicked, offer a What's This messageBox to help users. * @author Sébastien Laoût */ class HelpLabel : public KUrlLabel { Q_OBJECT public: HelpLabel(const QString &text, const QString &message, QWidget *parent); ~HelpLabel() override; QString message() { return m_message; } public slots: void setMessage(const QString &message) { m_message = message; } void display(); private: QString m_message; }; /** A dialog to choose the size of an icon. * @author Sébastien Laoût */ class IconSizeDialog : public QDialog { Q_OBJECT public: IconSizeDialog(const QString &caption, const QString &message, const QString &icon, int iconSize, QWidget *parent); ~IconSizeDialog() override; int iconSize() { return m_iconSize; } /// << @return the chosen icon size (16, 32, ...) or -1 if canceled! protected slots: void slotCancel(); void slotSelectionChanged(); void choose(QListWidgetItem *); private: QListWidgetItem *m_size16; QListWidgetItem *m_size22; QListWidgetItem *m_size32; QListWidgetItem *m_size48; QListWidgetItem *m_size64; QListWidgetItem *m_size128; int m_iconSize; QPushButton *okButton; }; /** * A missing class from Frameworks (and Qt): a combobox to select a font size! */ class FontSizeCombo : public KComboBox { Q_OBJECT public: FontSizeCombo(bool rw, bool withDefault, QWidget *parent = 0); ~FontSizeCombo() override; void setFontSize(qreal size); qreal fontSize(); protected: void keyPressEvent(QKeyEvent *event) override; signals: void sizeChanged(qreal size); void escapePressed(); void returnPressed2(); private slots: void textChangedInCombo(const QString &text); private: bool m_withDefault; }; #endif // VARIOUSWIDGETS_H diff --git a/src/xmlwork.cpp b/src/xmlwork.cpp index eda4933..947657b 100644 --- a/src/xmlwork.cpp +++ b/src/xmlwork.cpp @@ -1,118 +1,105 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "xmlwork.h" #include #include #include #include QDomDocument *XMLWork::openFile(const QString &name, const QString &filePath) { QDomDocument *doc = new QDomDocument(name); QFile file(filePath); if (!file.open(QIODevice::ReadOnly)) { // QMessageBox::information(this, "Load an XML file", "Error : un-openable file"); delete doc; return 0; } if (!doc->setContent(&file)) { // QMessageBox::information(this, "Load an XML file", "Error : malformed content"); file.close(); delete doc; return 0; } file.close(); return doc; } QDomElement XMLWork::getElement(const QDomElement &startElement, const QString &elementPath) { QStringList elements = elementPath.split('/'); QDomNode n = startElement.firstChild(); for (int i = 0; i < elements.count(); ++i) { // For each elements while (!n.isNull()) { // Browse their sub elements QDomElement e = n.toElement(); // and search the good one if ((!e.isNull()) && e.tagName() == elements.at(i)) { // If found if (i + 1 == elements.count()) // And if it is the asked element return e; // Return the first corresponding else { // Or if it is an intermediate element n = e.firstChild(); // Continue with the next sub element break; } } n = n.nextSibling(); } } return QDomElement(); // Not found ! } QString XMLWork::getElementText(const QDomElement &startElement, const QString &elementPath, const QString &defaultTxt) { QDomElement e = getElement(startElement, elementPath); if (e.isNull()) return defaultTxt; else return e.text(); } void XMLWork::addElement(QDomDocument &document, QDomElement &parent, const QString &name, const QString &text) { QDomElement tag = document.createElement(name); parent.appendChild(tag); QDomText content = document.createTextNode(text); tag.appendChild(content); } bool XMLWork::trueOrFalse(const QString &value, bool defaultValue) { if (value == "true" || value == "1" || value == "on" || value == "yes") return true; if (value == "false" || value == "0" || value == "off" || value == "no") return false; return defaultValue; } QString XMLWork::trueOrFalse(bool value) { return value ? "true" : "false"; } QString XMLWork::innerXml(QDomElement &element) { QString inner; for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) if (n.isCharacterData()) inner += n.toCharacterData().data(); else if (n.isElement()) { QDomElement e = n.toElement(); inner += '<' + e.tagName() + '>' + innerXml(e) + "'; } return inner; } void XMLWork::setupXmlStream(QXmlStreamWriter &stream, QString startElement) { stream.setAutoFormatting(true); stream.setAutoFormattingIndent(1); stream.writeStartDocument(); stream.writeDTD("'); stream.writeStartElement(startElement); } diff --git a/src/xmlwork.h b/src/xmlwork.h index 1d100b9..31c2315 100644 --- a/src/xmlwork.h +++ b/src/xmlwork.h @@ -1,47 +1,34 @@ -/*************************************************************************** - * Copyright (C) 2003 by Sébastien Laoût * - * * - * 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., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/** + * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef XMLWORKXMLWORK_H #define XMLWORKXMLWORK_H #include #include class QDomDocument; class QDomElement; /** All related functions to manage XML files and trees * @author Sébastien Laoût */ namespace XMLWork { // Manage XML files : QDomDocument *openFile(const QString &name, const QString &filePath); // Manage XML trees : QDomElement getElement(const QDomElement &startElement, const QString &elementPath); QString getElementText(const QDomElement &startElement, const QString &elementPath, const QString &defaultTxt = ""); void addElement(QDomDocument &document, QDomElement &parent, const QString &name, const QString &text); QString innerXml(QDomElement &element); void setupXmlStream(QXmlStreamWriter &stream, QString startElement); ///< Set XML options and write document start // Not directly related to XML : bool trueOrFalse(const QString &value, bool defaultValue = true); QString trueOrFalse(bool value); } #endif // XMLWORKXMLWORK_H