From 052d5385d6ed31ab5fd294e2f4265e6f32ded1e3 Mon Sep 17 00:00:00 2001 From: Packit Bot Date: May 04 2021 22:11:41 +0000 Subject: adwaita-qt-1.2.1 base --- diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ec8bf55 --- /dev/null +++ b/.clang-format @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: MIT +# +# Copyright (C) 2019 Christoph Cullmann +# Copyright (C) 2019 Gernot Gebhard +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Style for C++ +Language: Cpp + +# base is WebKit coding style: http://www.webkit.org/coding/coding-style.html +# below are only things set that diverge from this style! +BasedOnStyle: WebKit + +# enforce C++11 (e.g. for std::vector> +Standard: Cpp11 + +# 4 spaces indent +TabWidth: 4 + +# 3 * 80 wide lines +ColumnLimit: 240 + +# sort includes inside line separated groups +SortIncludes: true + +# break before braces on function, namespace and class definitions. +BreakBeforeBraces: Linux + +# CrlInstruction *a; +PointerAlignment: Right + +# horizontally aligns arguments after an open bracket. +AlignAfterOpenBracket: Align + +# align trailing comments +AlignTrailingComments: true + +# don't move all parameters to new line +AllowAllParametersOfDeclarationOnNextLine: false + +# no single line functions +AllowShortFunctionsOnASingleLine: None + +# always break before you encounter multi line strings +AlwaysBreakBeforeMultilineStrings: true + +# don't move arguments to own lines if they are not all on the same +BinPackArguments: false + +# don't move parameters to own lines if they are not all on the same +BinPackParameters: false + +# don't break binary ops +BreakBeforeBinaryOperators: None + +# format C++11 braced lists like function calls +Cpp11BracedListStyle: true + +# remove empty lines +KeepEmptyLinesAtTheStartOfBlocks: false + +# no namespace indentation to keep indent level low +NamespaceIndentation: None + +# we use template< without space. +SpaceAfterTemplateKeyword: false + +# macros for which the opening brace stays attached. +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1c593f9 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,71 @@ +name: Automatic build + +on: + push: + branches: + - '**' + pull_request: + branches: + - '**' + release: + types: [ created ] +env: + BUILD_TYPE: Release + +jobs: + Linux: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: | + sudo apt update + sudo apt install qt5-default libqt5gui5 libqt5x11extras5 cmake pkg-config qtbase5-dev libqt5x11extras5-dev qtbase5-private-dev libx11-dev xcb libx11-xcb-dev + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_EXAMPLE=true + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + run: make -j2 + + - name: Install + working-directory: ${{runner.workspace}}/build + shell: bash + run: sudo make install + + #- name: Test + #working-directory: ${{runner.workspace}}/build + #shell: bash + #run: | + #cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_EXAMPLE=true -DBUILD_TESTING=true + #make -j2 + #make test + Windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v1 + with: + fetch-depth: 1 + + - name: Install Qt + uses: jurplel/install-qt-action@v2 + with: + cached: ${{ steps.cache-qt.outputs.cache-hit }} + arch: 'win64_mingw81' + version: '5.15.2' + + - name: Build + shell: bash + if: ${{ !steps.cache-qt.outputs.cache-hit }} + run: | + mkdir build + cd build + cmake -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX="$Qt5_Dir" -DBUILD_SHARED_LIBS=ON .. + mingw32-make -j2 VERBOSE=1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7dfef6d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.kdev4 +build +CMakeLists.txt.user* + diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..3723899 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,10 @@ +Cursors: + Ken Vermette + +KStyle (original Breeze): + Hugo Pereira Da Costa - Developer + Andrew Lake - Designer + +KStyle (adwaita modifications) + Martin Bříza + Jan Grulich diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4360e0a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,72 @@ +project(Adwaita) + +cmake_minimum_required(VERSION 3.0) + +set(ADWAITAQT_VERSION_MAJOR 1) +set(ADWAITAQT_VERSION "1.2.1") + +set(QT_MIN_VERSION "5.12.0") +set(CMAKE_AUTOMOC ON) + +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/ ${CMAKE_MODULE_PATH}) + +include(GNUInstallDirs) + +add_definitions(-std=c++11) + +find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS + Core + DBus + Gui + Widgets +) + +if (NOT APPLE AND NOT WIN32) + find_package(XCB 1.10 COMPONENTS XCB) + + set(ADWAITA_HAVE_X11 ${XCB_FOUND}) + + find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS + X11Extras + ) +endif() + +get_target_property(REAL_QMAKE_EXECUTABLE ${Qt5Core_QMAKE_EXECUTABLE} + IMPORTED_LOCATION) +if (NOT QT_PLUGINS_DIR) + execute_process(COMMAND "${REAL_QMAKE_EXECUTABLE}" -query QT_INSTALL_PLUGINS + OUTPUT_VARIABLE QT_PLUGINS_DIR + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() + +add_subdirectory(src) + +# Tests need to be build from the root directory +# We now only test whether we can find and build against Adwaita library +if (BUILD_TESTING) + enable_testing() + + find_package(Qt5 ${QT_MIN_VERSION} NO_MODULE REQUIRED Test) + + find_package(PkgConfig REQUIRED) + pkg_check_modules(ADWAITAQT adwaita-qt>=${ADWAITAQT_VERSION}) + + if (NOT ADWAITAQT_FOUND) + message(FATAL_ERROR "Unable to find Adwaita-qt using PkgConfig") + endif() + + find_package(AdwaitaQt ${ADWAITAQT_VERSION} REQUIRED) + + set(test_SRCS + tests/test.cpp + ) + + add_executable(test-pkgconfig ${test_SRCS}) + target_link_libraries(test-pkgconfig Qt5::Test ${ADWAITAQT_LIBRARIES}) + + add_executable(test-cmake ${test_SRCS}) + target_link_libraries(test-cmake Qt5::Test AdwaitaQt) + + add_test(NAME TestPkgConfig COMMAND test-pkgconfig) + add_test(NAME TestCmake COMMAND test-cmake) +endif() diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..5185fd3 --- /dev/null +++ b/COPYING @@ -0,0 +1,346 @@ +NOTE! The GPL below is copyrighted by the Free Software Foundation, but +the instance of code that it refers to (the kde programs) are copyrighted +by the authors who actually wrote it. + +--------------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 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/LICENSE.GPL2 b/LICENSE.GPL2 new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/LICENSE.GPL2 @@ -0,0 +1,339 @@ + 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. + + 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., + 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/LICENSE.LGPL2 b/LICENSE.LGPL2 new file mode 100644 index 0000000..ec47efc --- /dev/null +++ b/LICENSE.LGPL2 @@ -0,0 +1,504 @@ + 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. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + 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 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. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..52203af --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +adwaita-qt +========== + +A native style to bend Qt5 applications to look like they belong into GNOME Shell. + +![Widget Factory](data/screenshots/widgets.png) + +![KCalc](data/screenshots/kcalc.png) + +## How to compile + +The project uses the standard CMake buildsystem. + +So for example, the whole compilation process could look like this: + +``` +mkdir build +cd build +cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr .. +make +make install +``` + +## Usage + +After install, you'll be able to either set the theme as your default via your DE's tools (like `systemsettings` or `qt-config`) or start your qt applications with the `-style adwaita` parameter. diff --git a/cmake/FindXCB.cmake b/cmake/FindXCB.cmake new file mode 100644 index 0000000..7ecd524 --- /dev/null +++ b/cmake/FindXCB.cmake @@ -0,0 +1,55 @@ +# Try to find XCB on a Unix system +# +# This will define: +# +# XCB_FOUND - True if xcb is available +# XCB_LIBRARIES - Link these to use xcb +# XCB_INCLUDE_DIR - Include directory for xcb +# XCB_DEFINITIONS - Compiler flags for using xcb +# +# In addition the following more fine grained variables will be defined: +# +# XCB_XCB_FOUND XCB_XCB_INCLUDE_DIR XCB_XCB_LIBRARIES +# +# Copyright (c) 2012 Fredrik Höglund +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +if (XCB_INCLUDE_DIR AND XCB_LIBRARIES) + # In the cache already + set(XCB_FIND_QUIETLY TRUE) +endif (XCB_INCLUDE_DIR AND XCB_LIBRARIES) + +# Use pkg-config to get the directories and then use these values +# in the FIND_PATH() and FIND_LIBRARY() calls +find_package(PkgConfig) +pkg_check_modules(PKG_XCB QUIET xcb) + +set(XCB_DEFINITIONS ${PKG_XCB_CFLAGS}) + +find_path(XCB_XCB_INCLUDE_DIR + NAMES xcb/xcb.h + HINTS ${PKG_XCB_INCLUDE_DIRS}) + +find_library(XCB_XCB_LIBRARIES + NAMES xcb + HINTS ${PKG_XCB_LIBRARY_DIRS}) + +set(XCB_INCLUDE_DIR ${XCB_XCB_INCLUDE_DIR}) + +set(XCB_LIBRARIES ${XCB_XCB_LIBRARIES}) + +list(REMOVE_DUPLICATES XCB_INCLUDE_DIR) + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(XCB + FOUND_VAR + XCB_FOUND + REQUIRED_VARS + XCB_LIBRARIES + XCB_INCLUDE_DIR +) + +mark_as_advanced(XCB_INCLUDE_DIR XCB_LIBRARIES) diff --git a/data/screenshots/kcalc.png b/data/screenshots/kcalc.png new file mode 100644 index 0000000..8d9d959 Binary files /dev/null and b/data/screenshots/kcalc.png differ diff --git a/data/screenshots/widgets.png b/data/screenshots/widgets.png new file mode 100644 index 0000000..95f4203 Binary files /dev/null and b/data/screenshots/widgets.png differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..786513a --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,7 @@ +option(BUILD_EXAMPLE "Build an example widget factory app" OFF) +if (BUILD_EXAMPLE) + add_subdirectory(demo) +endif() + +add_subdirectory(lib) +add_subdirectory(style) diff --git a/src/demo/CMakeLists.txt b/src/demo/CMakeLists.txt new file mode 100644 index 0000000..ab84c38 --- /dev/null +++ b/src/demo/CMakeLists.txt @@ -0,0 +1,16 @@ + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +set(showcase_SRCS + main.cpp + widgetfactory.cpp +) + +set(showcase_UI + widgetfactory.ui +) + +qt5_wrap_ui(showcase_SRCS ${showcase_UI}) + +add_executable(showcase ${showcase_SRCS}) +target_link_libraries(showcase Qt5::Core Qt5::Widgets) diff --git a/src/demo/main.cpp b/src/demo/main.cpp new file mode 100644 index 0000000..fc5d6e7 --- /dev/null +++ b/src/demo/main.cpp @@ -0,0 +1,32 @@ +/* + * Widget Showcase + * Copyright (C) 2014-2018 Martin Bříza + * Copyright (C) 2019 Jan Grulich + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include + +#include "widgetfactory.h" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + WidgetFactory fact; + fact.show(); + return app.exec(); +} diff --git a/src/demo/widgetfactory.cpp b/src/demo/widgetfactory.cpp new file mode 100644 index 0000000..19ead6b --- /dev/null +++ b/src/demo/widgetfactory.cpp @@ -0,0 +1,166 @@ +/* + * Widget Showcase + * Copyright (C) 2014-2018 Martin Bříza + * Copyright (C) 2019 Jan Grulich + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include + +#include "widgetfactory.h" +#include "ui_widgetfactory.h" + +#include +#include +#include +#include +#include + +class WidgetFactory::Private +{ +public: + Private() { } + Ui_WidgetFactory ui; +}; + +WidgetFactory::WidgetFactory(QWidget *parent) + : QMainWindow(parent) + , d(new WidgetFactory::Private()) +{ + setGeometry(0, 0, 1440, 800); + + QWidget *mainWidget = new QWidget(this); + d->ui.setupUi(mainWidget); + + setCentralWidget(mainWidget); + + // QLineEdit with the icon inside + QAction *lineEditAction = new QAction(QIcon::fromTheme(QLatin1String("view-refresh")), QString()); + connect(lineEditAction, &QAction::triggered, this, [this] (bool checked) { + if (d->ui.linedit->echoMode() == QLineEdit::Normal) { + d->ui.linedit->setEchoMode(QLineEdit::Password); + } else { + d->ui.linedit->setEchoMode(QLineEdit::Normal); + } + }); + d->ui.linedit->addAction(lineEditAction, QLineEdit::TrailingPosition); + + // QLineEdit with the widget on the right side + connect(d->ui.button, &QPushButton::pressed, this, [this] () { + d->ui.lineedit2->clear(); + }); + + // Checkboxes + d->ui.checkbox3->setCheckState(Qt::PartiallyChecked); + d->ui.checkbox6->setCheckState(Qt::PartiallyChecked); + + // Buttons + d->ui.pushbutton3->setDown(true); + d->ui.pushbutton4->setDown(true); + + // Sliders and progress bars + connect(d->ui.horizontalslider3, &QSlider::valueChanged, this, [this] (int value) { + d->ui.progressbar_horizontal->setValue(value); + d->ui.progressbar_horizontal2->setValue(value); + d->ui.progressbar_vertical->setValue(value); + d->ui.progressbar_vertical2->setValue(value); + }); + + connect(d->ui.horizontalslider, &QSlider::valueChanged, this, [this] (int value) { + d->ui.horizontalslider2->setValue(value); + d->ui.verticalslider->setValue(value); + d->ui.verticalslider2->setValue(value); + }); + connect(d->ui.verticalslider, &QSlider::valueChanged, this, [this] (int value) { + d->ui.horizontalslider->setValue(value); + d->ui.horizontalslider2->setValue(value); + d->ui.verticalslider2->setValue(value); + }); + + QMenuBar *menubar = new QMenuBar(this); + setMenuBar(menubar); + // Inspired by Kondike (a gtk game) + QMenu *game = new QMenu("Game", menubar); + QAction *newGame = new QAction("New game"); + newGame->setShortcut(QKeySequence::New); + game->addAction(newGame); + QAction *restart = new QAction("Restart"); + restart->setDisabled(true); + game->addAction(restart); + QAction *statistics = new QAction("Statistics"); + game->addAction(statistics); + QAction *selectGame = new QAction("Select game..."); + selectGame->setShortcut(QKeySequence::Open); + game->addAction(selectGame); + QMenu *recentlyPlayed = new QMenu("Recently played"); + recentlyPlayed->addAction(new QAction("Game 1", recentlyPlayed)); + recentlyPlayed->addAction(new QAction("Game 1", recentlyPlayed)); + game->addMenu(recentlyPlayed); + game->addSeparator(); + QAction *close = new QAction("Close"); + close->setShortcut(QKeySequence::Quit); + game->addAction(close); + menuBar()->insertMenu(nullptr, game); + + QMenu *edit = new QMenu("Edit", menubar); + QAction *act1 = new QAction("Pick me"); + act1->setCheckable(true); + act1->setChecked(true); + QAction *act2 = new QAction("No, pick me instead"); + act2->setCheckable(true); + QAction *act3 = new QAction("Don't pick me"); + act3->setCheckable(true); + act3->setDisabled(true); + edit->addAction(act1); + edit->addAction(act2); + edit->addAction(act3); + QActionGroup *actionGroup = new QActionGroup(edit); + actionGroup->setExclusive(true); + actionGroup->addAction(act1); + actionGroup->addAction(act2); + actionGroup->addAction(act3); + menuBar()->insertMenu(nullptr, edit); + + menuBar()->insertMenu(nullptr, new QMenu("View", menubar)); + QAction *testAction = new QAction(QStringLiteral("Test 1")); + testAction->setCheckable(true); + + edit->addAction(testAction); + menuBar()->insertMenu(nullptr, edit); + menuBar()->insertMenu(nullptr, new QMenu("Help")); + + QToolBar *toolbar = new QToolBar(); + toolbar->setMovable(false); + toolbar->setFloatable(false); + toolbar->addAction(QIcon::fromTheme("document-save"), ""); + toolbar->addAction(QIcon::fromTheme("document-open"), ""); + toolbar->addSeparator(); + toolbar->addAction(QIcon::fromTheme("edit-find"), ""); + QWidget *spacer = new QWidget; + spacer->setPalette(QPalette(Qt::transparent)); + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + toolbar->addWidget(spacer); + toolbar->addWidget(new QLineEdit("search...")); + addToolBar(Qt::TopToolBarArea, toolbar); + + setWindowTitle(tr("Qt/GTK+ Widget Factory")); +} + +WidgetFactory::~WidgetFactory() +{ + delete d; +} diff --git a/src/demo/widgetfactory.h b/src/demo/widgetfactory.h new file mode 100644 index 0000000..b6446af --- /dev/null +++ b/src/demo/widgetfactory.h @@ -0,0 +1,39 @@ +/* + * Widget Showcase + * Copyright (C) 2014-2018 Martin Bříza + * Copyright (C) 2019 Jan Grulich + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef WIDGETFACTORY_H +#define WIDGETFACTORY_H + +#include + +class WidgetFactory : public QMainWindow +{ + Q_OBJECT +public: + WidgetFactory(QWidget *parent = nullptr); + ~WidgetFactory(); + +private: + class Private; + Private *d; +}; + +#endif // WIDGETFACTORY_H diff --git a/src/demo/widgetfactory.ui b/src/demo/widgetfactory.ui new file mode 100644 index 0000000..99d904b --- /dev/null +++ b/src/demo/widgetfactory.ui @@ -0,0 +1,1138 @@ + + + WidgetFactory + + + + 0 + 0 + 1371 + 800 + + + + Form + + + + + + QComboBox + + + true + + + + Donald Duck + + + + + Mickey Mouse + + + + + Jet McQuack + + + + + + + + QLayout::SetDefaultConstraint + + + + + + 0 + 112 + + + + QFrame::StyledPanel + + + QFrame::Plain + + + + + + QDockWidget::NoDockWidgetFeatures + + + + + + + + + 163 + 96 + + + + Plain + + + + + + + QDockWidget::DockWidgetMovable + + + + + + + + + + + + 179 + 113 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Raised + + + + + + + + + + + 179 + 112 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Sunken + + + + + + + + + + + + false + + + QComboBox + + + true + + + + comboboxentry + + + + + + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QComboBox + + + + Left + + + + + Middle + + + + + Right + + + + + + + + QComboBox + + + 1 + + + + Left + + + + + Middle + + + + + Right + + + + + + + + QComboBox + + + 2 + + + + Left + + + + + Middle + + + + + Right + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + PushButton + + + + + + + false + + + PushButton + + + + + + + true + + + PushButton + + + false + + + false + + + false + + + + + + + false + + + PushButton + + + + + + + + Andrea + + + + + Otto + + + + + Orville + + + + + Benjamin + + + + + + + + false + + + + Andrea + + + + + Otto + + + + + Orville + + + + + Benjamin + + + + + + + + <a href="https://github.com/FedoraQt/adwaita-qt">link button</a> + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + QLabel + + + widgets around + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + QLabel + + + have tooltips + + + + + + + QSpinBox + + + 50 + + + + + + + false + + + QSpinBox + + + + + + + + + QLineEdit + + + Click icon to change echo mode + + + false + + + + + + + + + 50 + + + + + + + 50 + + + true + + + QProgressBar::TopToBottom + + + + + + + 0 + + + 0 + + + 50 + + + true + + + false + + + + + + + 100 + + + Qt::Horizontal + + + + + + + false + + + 100 + + + Qt::Horizontal + + + false + + + + + + + 100 + + + 25 + + + 25 + + + 50 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 25 + + + + + + + + + 50 + + + Qt::Vertical + + + true + + + + + + + 50 + + + Qt::Vertical + + + false + + + + + + + 100 + + + Qt::Vertical + + + true + + + QSlider::TicksAbove + + + + + + + false + + + 100 + + + Qt::Vertical + + + true + + + QSlider::TicksBelow + + + + + + + + + + + 0 + + + QLayout::SetDefaultConstraint + + + + + QLineEdit + + + entry + + + + + + + + + + + .. + + + + + + + + + false + + + false + + + true + + + true + + + false + + + 4 + + + false + + + 50 + + + false + + + true + + + + Cool + + + + + Icon + + + + + Name + + + + + Nick + + + + + + + + Checked + + + + + + + .. + + + Andrea + + + Cimi + + + + + + + + Unchecked + + + + + + + .. + + + Otto + + + Chaotic + + + ItemIsSelectable|ItemIsDragEnabled|ItemIsDropEnabled|ItemIsUserCheckable|ItemIsEnabled|ItemIsTristate + + + + + + + + Checked + + + + + + + .. + + + Orville + + + Redenbacher + + + + + + + + PartiallyChecked + + + + + + + .. + + + Benjamin + + + Company + + + + + + + + + + + + + + QCheckBox + + + checkbutton + + + true + + + + + + + QRadioButton + + + RadioButton + + + true + + + false + + + + + + + + + + + QCheckBox + + + checkbutton + + + + + + + QRadioButton + + + RadioButton + + + + + + + + + + + QCheckBox + + + checkbutton + + + true + + + false + + + + + + + QRadioButton + + + RadioButton + + + + + + + + + + + false + + + QCheckBox + + + checkbutton + + + true + + + + + + + false + + + QRadioButton + + + RadioButton + + + true + + + false + + + + + + + + + + + false + + + QCheckBox + + + checkbutton + + + + + + + false + + + QRadioButton + + + RadioButton + + + false + + + + + + + + + + + false + + + QCheckBox + + + checkbutton + + + + + + + false + + + QRadioButton + + + RadioButton + + + false + + + + + + + + + + + Qt::Horizontal + + + + 228 + 20 + + + + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Nullam fringilla, est ut feugiat ultrices, elit lacus ultricies nibh, id commodo tortor nisi id elit.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Morbi vel elit erat. Maecenas dignissim, dui et pharetra rutrum, tellus lectus rutrum mi, a convallis libero nisi quis tellus.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Nulla facilisi. Nullam eleifend lobortis nisl, in porttitor tellus malesuada vitae.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Aenean lacus tellus, pellentesque quis molestie quis, fringilla in arcu.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Duis elementum, tellus sed tristique semper, metus metus accumsan augue, et porttitor augue orci a libero.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Ut sed justo ac felis placerat laoreet sed id sem. Proin mattis tincidunt odio vitae tristique.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Morbi massa libero, congue vitae scelerisque vel, ultricies vel nisl.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Vestibulum in tortor diam, quis aliquet quam. Praesent ut justo neque, tempus rutrum est.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Duis eu lectus quam. Vivamus eget metus a mauris molestie venenatis pulvinar eleifend nisi.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Nulla facilisi. Pellentesque at dolor sit amet purus dapibus pulvinar molestie quis neque.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Suspendisse feugiat quam quis dolor accumsan cursus.</p></body></html> + + + + + + + + + 2 + + + + Page 1 + + + + + Page 2 + + + + + Page 3 + + + + + + + + QTabWidget::East + + + 2 + + + + Page 1 + + + + + Page 2 + + + + + Page 3 + + + + + + + + QTabWidget::South + + + 2 + + + + Page 1 + + + + + Page 2 + + + + + Page 3 + + + + + + + + QTabWidget::West + + + 2 + + + + Page 1 + + + + + Page 2 + + + + + Page3 + + + + + + + + + + false + + + Disabled lineedit + + + + + + + + diff --git a/src/lib/AdwaitaQtConfig.cmake.in b/src/lib/AdwaitaQtConfig.cmake.in new file mode 100644 index 0000000..b4f6925 --- /dev/null +++ b/src/lib/AdwaitaQtConfig.cmake.in @@ -0,0 +1,10 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(Qt5Core @QT_MIN_VERSION@) +find_dependency(Qt5Gui @QT_MIN_VERSION@) +find_dependency(Qt5Widgets @QT_MIN_VERSION@) + +set(ADWAITAQT_LIBRARIES adwaitaqt) +set(ADWAITAQT_INCLUDE_DIRS @CMAKE_INSTALL_FULL_INCLUDEDIR@) +include("${CMAKE_CURRENT_LIST_DIR}/AdwaitaQtTargets.cmake") diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt new file mode 100644 index 0000000..ebfd083 --- /dev/null +++ b/src/lib/CMakeLists.txt @@ -0,0 +1,121 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +configure_file(config-adwaita.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-adwaita.h ) +configure_file(adwaita-qt.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/adwaita-qt.pc @ONLY) + +set(libadwaitaqt_priv_SRCS + # Animations + animations/adwaitaanimation.cpp + animations/adwaitaanimations.cpp + animations/adwaitaanimationdata.cpp + animations/adwaitabaseengine.cpp + animations/adwaitabusyindicatordata.cpp + animations/adwaitabusyindicatorengine.cpp + animations/adwaitadialdata.cpp + animations/adwaitadialengine.cpp + animations/adwaitaenabledata.cpp + animations/adwaitagenericdata.cpp + animations/adwaitaheaderviewdata.cpp + animations/adwaitaheaderviewengine.cpp + animations/adwaitascrollbardata.cpp + animations/adwaitascrollbarengine.cpp + animations/adwaitaspinboxengine.cpp + animations/adwaitaspinboxdata.cpp + animations/adwaitastackedwidgetdata.cpp + animations/adwaitastackedwidgetengine.cpp + animations/adwaitatabbarengine.cpp + animations/adwaitatabbardata.cpp + animations/adwaitatoolboxengine.cpp + animations/adwaitatransitiondata.cpp + animations/adwaitatransitionwidget.cpp + animations/adwaitawidgetstateengine.cpp + animations/adwaitawidgetstatedata.cpp + + # Others + adwaitaaddeventfilter.cpp + adwaitamnemonics.cpp + adwaitasplitterproxy.cpp + adwaitawidgetexplorer.cpp + adwaitawindowmanager.cpp +) + +set(libadwaitaqt_SRCS + adwaita.cpp + adwaitacolors.cpp +) + +set(libadwaitaqt_HEADERS + adwaita.h + adwaitacolors.h + adwaitaqt_export.h +) + +add_library(adwaitaqtpriv SHARED ${libadwaitaqt_priv_SRCS}) +target_link_libraries(adwaitaqtpriv + Qt5::Core + Qt5::DBus + Qt5::Gui + Qt5::Widgets +) + +add_library(adwaitaqt SHARED ${libadwaitaqt_SRCS}) +add_library(AdwaitaQt ALIAS adwaitaqt) + +target_link_libraries(adwaitaqt + PUBLIC + Qt5::Core + Qt5::Gui + Qt5::Widgets + PRIVATE + adwaitaqtpriv +) + +set_target_properties(adwaitaqtpriv PROPERTIES VERSION ${ADWAITAQT_VERSION} SOVERSION 1) +if(MINGW AND BUILD_SHARED_LIBS) + get_target_property(ADWAITA_QT_SOVERSION adwaitaqtpriv SOVERSION) + set_target_properties(adwaitaqtpriv PROPERTIES SUFFIX "-${ADWAITA_QT_SOVERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}") +endif() + +set_target_properties(adwaitaqt PROPERTIES VERSION ${ADWAITAQT_VERSION} SOVERSION 1) +if(MINGW AND BUILD_SHARED_LIBS) + get_target_property(ADWAITA_QT_SOVERSION adwaitaqt SOVERSION) + set_target_properties(adwaitaqt PROPERTIES SUFFIX "-${ADWAITA_QT_SOVERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}") +endif() + +target_include_directories(adwaitaqt PUBLIC ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS}) + +install(TARGETS adwaitaqtpriv EXPORT AdwaitaQtTargets RUNTIME DESTINATION bin LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +install(TARGETS adwaitaqt EXPORT AdwaitaQtTargets RUNTIME DESTINATION bin LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +install(FILES ${libadwaitaqt_HEADERS} DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/AdwaitaQt) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/adwaita-qt.pc DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/pkgconfig) + +# create a Config.cmake and a ConfigVersion.cmake file and install them +set(CMAKECONFIG_INSTALL_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/cmake/AdwaitaQt") + +include(CMakePackageConfigHelpers) + +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/AdwaitaQtConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/AdwaitaQtConfig.cmake" + INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} +) + +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/AdwaitaQtConfigVersion.cmake + VERSION ${ADWAITAQT_VERSION} + COMPATIBILITY SameMinorVersion +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/AdwaitaQtConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/AdwaitaQtConfigVersion.cmake" + DESTINATION "${CMAKECONFIG_INSTALL_DIR}" + COMPONENT Devel +) + +install(EXPORT AdwaitaQtTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE AdwaitaQtTargets.cmake) diff --git a/src/lib/adwaita-qt.pc.cmake b/src/lib/adwaita-qt.pc.cmake new file mode 100644 index 0000000..ee30f50 --- /dev/null +++ b/src/lib/adwaita-qt.pc.cmake @@ -0,0 +1,13 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ + +Name: adwaita-qt +Description: Qt Adwaita Style +Version: @ADWAITAQT_VERSION@ +Requires: @PC_REQUIRES@ +@PC_REQUIRES_PRIVATE@ + +Libs: -L${libdir} -ladwaitaqt +Cflags: -I${includedir} +Requires: Qt5Core Qt5Widgets diff --git a/src/lib/adwaita.cpp b/src/lib/adwaita.cpp new file mode 100644 index 0000000..1cb9bd4 --- /dev/null +++ b/src/lib/adwaita.cpp @@ -0,0 +1,235 @@ + +/************************************************************************* + * Copyright (C) 2020 Jan Grulich * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaita.h" +#include "animations/adwaitaanimations.h" + +namespace Adwaita +{ + +class StyleOptionsPrivate +{ +public: + explicit StyleOptionsPrivate(const QPalette &palette) + : m_palette(palette) + { } + virtual ~StyleOptionsPrivate() + { } + + QPalette m_palette; + QPalette::ColorGroup m_colorGroup = QPalette::ColorGroup::Normal; + QPalette::ColorRole m_colorRole = QPalette::ColorRole::Base; + ColorVariant m_colorVariant = ColorVariant::Unknown; + bool m_active = false; + bool m_focus = false; + bool m_mouseHover = false; + qreal m_opacity = AnimationData::OpacityInvalid; + AnimationMode m_animationMode = AnimationNone; + CheckBoxState m_checkboxState = CheckOff; + QStyle::State m_state = QStyle::State_None; + bool m_inMenu = false; + bool m_sunken = false; +}; + +StyleOptions::StyleOptions(const QPalette &palette) + : d_ptr(new StyleOptionsPrivate(palette)) +{ +} + +StyleOptions::~StyleOptions() +{ +} + +QPalette StyleOptions::palette() const +{ + Q_D(const StyleOptions); + + return d->m_palette; +} + +void StyleOptions::setColorGroup(QPalette::ColorGroup group) +{ + Q_D(StyleOptions); + + d->m_colorGroup = group; +} + +QPalette::ColorGroup StyleOptions::colorGroup() const +{ + Q_D(const StyleOptions); + + return d->m_colorGroup; +} + +void StyleOptions::setColorRole(QPalette::ColorRole role) +{ + Q_D(StyleOptions); + + d->m_colorRole = role; +} + +QPalette::ColorRole StyleOptions::colorRole() const +{ + Q_D(const StyleOptions); + + return d->m_colorRole; +} + +void StyleOptions::setColorVariant(ColorVariant variant) +{ + Q_D(StyleOptions); + + d->m_colorVariant = variant; +} + +ColorVariant StyleOptions::colorVariant() const +{ + Q_D(const StyleOptions); + + return d->m_colorVariant; +} + +void StyleOptions::setActive(bool active) +{ + Q_D(StyleOptions); + + d->m_active = active; +} + +bool StyleOptions::active() const +{ + Q_D(const StyleOptions); + + return d->m_active; +} + +void StyleOptions::setHasFocus(bool focus) +{ + Q_D(StyleOptions); + + d->m_focus = focus; +} + +bool StyleOptions::hasFocus() const +{ + Q_D(const StyleOptions); + + return d->m_focus; +} + +void StyleOptions::setMouseOver(bool mouseOver) +{ + Q_D(StyleOptions); + + d->m_mouseHover = mouseOver; +} + +bool StyleOptions::mouseOver() const +{ + Q_D(const StyleOptions); + + return d->m_mouseHover; +} + +void StyleOptions::setOpacity(qreal opacity) +{ + Q_D(StyleOptions); + + d->m_opacity = opacity; +} + +qreal StyleOptions::opacity() const +{ + Q_D(const StyleOptions); + + return d->m_opacity; +} + +void StyleOptions::setAnimationMode(AnimationMode mode) +{ + Q_D(StyleOptions); + + d->m_animationMode = mode; +} + +AnimationMode StyleOptions::animationMode() const +{ + Q_D(const StyleOptions); + + return d->m_animationMode; +} + +void StyleOptions::setCheckboxState(CheckBoxState state) +{ + Q_D(StyleOptions); + + d->m_checkboxState = state; +} + +CheckBoxState StyleOptions::checkboxState() const +{ + Q_D(const StyleOptions); + + return d->m_checkboxState; +} + +void StyleOptions::setState(QStyle::State state) +{ + Q_D(StyleOptions); + + d->m_state = state; +} + +QStyle::State StyleOptions::state() const +{ + Q_D(const StyleOptions); + + return d->m_state; +} + +void StyleOptions::setInMenu(bool inMenu) +{ + Q_D(StyleOptions); + + d->m_inMenu = inMenu; +} + +bool StyleOptions::inMenu() const +{ + Q_D(const StyleOptions); + + return d->m_inMenu; +} + +void StyleOptions::setSunken(bool sunken) +{ + Q_D(StyleOptions); + + d->m_sunken = sunken; +} + +bool StyleOptions::sunken() const +{ + Q_D(const StyleOptions); + + return d->m_sunken; +} + +} // namespace Adwaita diff --git a/src/lib/adwaita.h b/src/lib/adwaita.h new file mode 100644 index 0000000..ba8306d --- /dev/null +++ b/src/lib/adwaita.h @@ -0,0 +1,342 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * Copyright (C) 2019 Jan Grulich * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_H +#define ADWAITA_H + +#include "adwaitaqt_export.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace Adwaita +{ + +//*@name convenience typedef +//@{ + +//* scoped pointer convenience typedef +template using WeakPointer = QPointer; + +//* scoped pointer convenience typedef +template using ScopedPointer = QScopedPointer; + +//@} + +//* Settings +namespace Settings +{ + const bool SingleClick {true}; + const bool ShowIconsOnPushButtons {true}; + const int ToolButtonStyle {Qt::ToolButtonTextBesideIcon}; + const bool ShowIconsInMenuItems {true}; +} + +enum EnumMnemonicsMode { MN_NEVER, MN_AUTO, MN_ALWAYS }; + +enum EnumWindowDragMode { WD_NONE, WD_MINIMAL, WD_FULL }; + +//* Config +namespace Config +{ + // Common + const int ShadowStrength {0}; + const int ShadowSize {0}; + const QColor ShadowColor {Qt::transparent}; + const bool OutlineCloseButton {false}; + + // Style + const bool AnimationsEnabled {true}; + const int AnimationSteps {100}; + const int AnimationsDuration {180}; + const bool StackedWidgetTransitionsEnabled {false}; + const bool ProgressBarAnimated {true}; + const int ProgressBarBusyStepDuration {600}; + const int ScrollBarAddLineButtons {0}; + const int ScrollBarSubLineButtons {0}; + const bool ScrollBarShowOnMouseOver {true}; + const int MnemonicsMode {MN_AUTO}; + const bool ToolBarDrawItemSeparator {0}; + const bool ViewDrawFocusIndicator {true}; + const bool SliderDrawTickMarks {true}; + const bool ViewDrawTreeBranchLines {true}; + const bool ViewInvertSortIndicator {true}; + const bool TabBarDrawCenteredTabs {false}; + const bool TitleWidgetDrawFrame {true}; + const bool DockWidgetDrawFrame {false}; + const bool SidePanelDrawFrame {false}; + const bool MenuItemDrawStrongFocus {true}; + const int WindowDragMode {0}; + const QStringList WindowDragWhiteList; + const QStringList WindowDragBlackList; + const bool UseWMMoveResize {true}; + const bool SplitterProxyEnabled {true}; + const int SplitterProxyWidth {3}; + const bool WidgetExplorerEnabled {false}; + const bool DrawWidgetRects {false}; +} + +namespace PropertyNames +{ + const char noAnimations[] = "_kde_no_animations"; + const char noWindowGrab[] = "_kde_no_window_grab"; + const char netWMForceShadow[] = "_KDE_NET_WM_FORCE_SHADOW"; + const char netWMSkipShadow[] = "_KDE_NET_WM_SKIP_SHADOW"; + const char sidePanelView[] = "_kde_side_panel_view"; + const char toolButtonAlignment[] = "_kde_toolButton_alignment"; + const char menuTitle[] = "_adwaita_toolButton_menutitle"; + const char alteredBackground[] = "_adwaita_altered_background"; +} + +//* metrics +enum Metrics { + // frames + Frame_FrameWidth = 4, + Frame_FrameRadius = 5, + + // layout + Layout_TopLevelMarginWidth = 10, + Layout_ChildMarginWidth = 6, + Layout_DefaultSpacing = 6, + + // line editors + LineEdit_FrameWidth = 3, + LineEdit_MarginHeight = 2, + LineEdit_MarginWidth = 8, + LineEdit_MinHeight = 36, + LineEdit_MinWidth = 80, + + // menu items + Menu_FrameWidth = 0, + MenuItem_MarginWidth = 4, + MenuItem_ItemSpacing = 6, + MenuItem_AcceleratorSpace = 16, + MenuButton_IndicatorWidth = 20, + + // combobox + ComboBox_FrameWidth = 4, + ComboBox_MarginHeight = 4, + ComboBox_MarginWidth = 8, + ComboBox_MinHeight = 36, + ComboBox_MinWidth = 80, + + // spinbox + SpinBox_FrameWidth = LineEdit_FrameWidth, + SpinBox_ArrowButtonWidth = 20, + SpinBox_MinHeight = 36, + SpinBox_MinWidth = 80, + SpinBox_MarginHeight = 4, + SpinBox_MarginWidth = 8, + + // groupbox title margin + GroupBox_TitleMarginWidth = 4, + + // buttons + Button_MinHeight = 36, + Button_MinWidth = 80, + Button_MarginHeight = 4, + Button_MarginWidth = 8, + Button_ItemSpacing = 4, + + // tool buttons + ToolButton_MarginWidth = 6, + ToolButton_ItemSpacing = 4, + ToolButton_InlineIndicatorWidth = 12, + + // checkboxes and radio buttons + CheckBox_Size = 22, + CheckBox_FocusMarginWidth = 3, + CheckBox_ItemSpacing = 4, + + // menubar items + MenuBarItem_MarginWidth = 8, + MenuBarItem_MarginHeight = 5, + + // scrollbars + ScrollBar_Extend = 14, + ScrollBar_SliderWidth = 8, + ScrollBar_MinSliderHeight = 24, + ScrollBar_NoButtonHeight = (ScrollBar_Extend - ScrollBar_SliderWidth) / 2, + ScrollBar_SingleButtonHeight = 0, + ScrollBar_DoubleButtonHeight = 0, + + // toolbars + ToolBar_FrameWidth = 2, + ToolBar_HandleExtent = 10, + ToolBar_HandleWidth = 6, + ToolBar_SeparatorWidth = 8, + ToolBar_ExtensionWidth = 20, + ToolBar_ItemSpacing = 0, + + // progressbars + ProgressBar_BusyIndicatorSize = 24, + ProgressBar_Thickness = 3, + ProgressBar_ItemSpacing = 3, + + // mdi title bar + TitleBar_MarginWidth = 4, + + // sliders + Slider_TickLength = 4, + Slider_TickMarginWidth = 6, + Slider_GrooveThickness = 3, + Slider_ControlThickness = 24, + + // tabbar + TabBar_TabMarginHeight = 9, + TabBar_TabMarginWidth = 8, + TabBar_TabMinWidth = 80, + TabBar_TabMinHeight = 36, + TabBar_TabItemSpacing = 8, + TabBar_TabOverlap = 1, + TabBar_BaseOverlap = 0, + + // tab widget + TabWidget_MarginWidth = 4, + + // toolbox + ToolBox_TabMinWidth = 80, + ToolBox_TabItemSpacing = 4, + ToolBox_TabMarginWidth = 8, + + // tooltips + ToolTip_FrameWidth = 3, + + // scroll areas + ScrollArea_FrameWidth = 2, + + // list headers + Header_MarginWidth = 3, + Header_ItemSpacing = 2, + Header_ArrowSize = 10, + + // tree view + ItemView_ArrowSize = 10, + ItemView_ItemMarginWidth = 3, + SidePanel_ItemMarginWidth = 4, + + // splitter + Splitter_SplitterWidth = 1, + + // shadow dimensions + Shadow_Overlap = 0 + +}; + +//* animation mode +enum AnimationMode { AnimationNone = 0, AnimationHover = 0x1, AnimationFocus = 0x2, AnimationEnable = 0x4, AnimationPressed = 0x8 }; +Q_DECLARE_FLAGS(AnimationModes, AnimationMode) + +//* corners +enum Corner { + CornerTopLeft = 0x1, + CornerTopRight = 0x2, + CornerBottomLeft = 0x4, + CornerBottomRight = 0x8, + CornersTop = CornerTopLeft | CornerTopRight, + CornersBottom = CornerBottomLeft | CornerBottomRight, + CornersLeft = CornerTopLeft | CornerBottomLeft, + CornersRight = CornerTopRight | CornerBottomRight, + AllCorners = CornerTopLeft | CornerTopRight | CornerBottomLeft | CornerBottomRight +}; +Q_DECLARE_FLAGS(Corners, Corner) + +//* sides +enum Side { SideNone = 0x0, SideLeft = 0x1, SideTop = 0x2, SideRight = 0x4, SideBottom = 0x8, AllSides = SideLeft | SideTop | SideRight | SideBottom }; +Q_DECLARE_FLAGS(Sides, Side) + +//* checkbox state +enum CheckBoxState { CheckOff, CheckPartial, CheckOn, CheckAnimated }; + +//* radio button state +enum RadioButtonState { RadioOff, RadioOn, RadioAnimated }; + +//* arrow orientation +enum ArrowOrientation { ArrowNone, ArrowUp, ArrowDown, ArrowLeft, ArrowRight }; + +//* button type +enum ButtonType { ButtonClose, ButtonMaximize, ButtonMinimize, ButtonRestore }; + +enum ColorVariant { Unknown, Adwaita, AdwaitaDark, AdwaitaHighcontrast }; + +class StyleOptionsPrivate; + +class ADWAITAQT_EXPORT StyleOptions +{ +public: + explicit StyleOptions(const QPalette &palette); + virtual ~StyleOptions(); + + QPalette palette() const; + + void setColorGroup(QPalette::ColorGroup group); + QPalette::ColorGroup colorGroup() const; + + void setColorRole(QPalette::ColorRole role); + QPalette::ColorRole colorRole() const; + + void setColorVariant(ColorVariant variant); + ColorVariant colorVariant() const; + + void setActive(bool active); + bool active() const; + + void setHasFocus(bool focus); + bool hasFocus() const; + + void setMouseOver(bool mouseOver); + bool mouseOver() const; + + void setOpacity(qreal opacity); + qreal opacity() const; + + void setAnimationMode(AnimationMode mode); + AnimationMode animationMode() const; + + void setCheckboxState(CheckBoxState state); + CheckBoxState checkboxState() const; + + void setState(QStyle::State state); + QStyle::State state() const; + + void setInMenu(bool inMenu); + bool inMenu() const; + + void setSunken(bool sunken); + bool sunken() const; + +private: + Q_DECLARE_PRIVATE(StyleOptions) + + const QScopedPointer d_ptr; +}; + +} // namespace Adwaita + +Q_DECLARE_OPERATORS_FOR_FLAGS(Adwaita::AnimationModes) +Q_DECLARE_OPERATORS_FOR_FLAGS(Adwaita::Corners) +Q_DECLARE_OPERATORS_FOR_FLAGS(Adwaita::Sides) + +#endif // ADWAITA_H diff --git a/src/lib/adwaitaaddeventfilter.cpp b/src/lib/adwaitaaddeventfilter.cpp new file mode 100644 index 0000000..76677e5 --- /dev/null +++ b/src/lib/adwaitaaddeventfilter.cpp @@ -0,0 +1,20 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitaaddeventfilter.h" diff --git a/src/lib/adwaitaaddeventfilter.h b/src/lib/adwaitaaddeventfilter.h new file mode 100644 index 0000000..e2bbc83 --- /dev/null +++ b/src/lib/adwaitaaddeventfilter.h @@ -0,0 +1,56 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_ADD_EVENT_FILTER_H +#define ADWAITA_ADD_EVENT_FILTER_H + +#include +#include + +#include "adwaitaqt_export.h" + +namespace Adwaita +{ + +class ADWAITAQT_EXPORT AddEventFilter : public QObject +{ + Q_OBJECT +public: + //* constructor + AddEventFilter() + : QObject() + { + } + + //* destructor + virtual ~AddEventFilter() + { + } + + //* event filter + /** blocks all AddChild events */ + virtual bool eventFilter(QObject *, QEvent *event) + { + return event->type() == QEvent::ChildAdded; + } +}; + +} // namespace Adwaita + +#endif // ADWAITA_ADD_EVENT_FILTER_H diff --git a/src/lib/adwaitacolors.cpp b/src/lib/adwaitacolors.cpp new file mode 100644 index 0000000..8b978fb --- /dev/null +++ b/src/lib/adwaitacolors.cpp @@ -0,0 +1,728 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * Copyright (C) 2014-2018 Martin Bříza * + * Copyright (C) 2019-2020 Jan Grulich * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitacolors.h" +#include "animations/adwaitaanimationdata.h" + +#include + +#include + +namespace Adwaita +{ + +//* contrast for arrow and treeline rendering +static const qreal arrowShade = 0.15; + +QColor Colors::alphaColor(QColor color, qreal alpha) +{ + if (alpha >= 0 && alpha < 1.0) { + color.setAlphaF(alpha * color.alphaF()); + } + return color; +} + +QColor Colors::darken(const QColor &color, qreal amount) +{ + qreal h, s, l, a; + color.getHslF(&h, &s, &l, &a); + + qreal lightness = l - amount; + if (lightness < 0) { + lightness = 0; + } + + return QColor::fromHslF(h, s, lightness, a); +} + +QColor Colors::desaturate(const QColor &color, qreal amount) +{ + qreal h, s, l, a; + color.getHslF(&h, &s, &l, &a); + + qreal saturation = s - amount; + if (saturation < 0) { + saturation = 0; + } + return QColor::fromHslF(h, saturation, l, a); +} + +QColor Colors::lighten(const QColor &color, qreal amount) +{ + qreal h, s, l, a; + color.getHslF(&h, &s, &l, &a); + + qreal lightness = l + amount; + if (lightness > 1) { + lightness = 1; + } + return QColor::fromHslF(h, s, lightness, a); +} + +QColor Colors::mix(const QColor &c1, const QColor &c2, qreal bias) +{ + auto mixQreal = [](qreal a, qreal b, qreal bias) { + return a + (b - a) * bias; + }; + + if (bias <= 0.0) { + return c1; + } + + if (bias >= 1.0) { + return c2; + } + + if (std::isnan(bias)) { + return c1; + + } + + qreal r = mixQreal(c1.redF(), c2.redF(), bias); + qreal g = mixQreal(c1.greenF(), c2.greenF(), bias); + qreal b = mixQreal(c1.blueF(), c2.blueF(), bias); + qreal a = mixQreal(c1.alphaF(), c2.alphaF(), bias); + + return QColor::fromRgbF(r, g, b, a); +} + +QColor Colors::transparentize(const QColor &color, qreal amount) +{ + qreal h, s, l, a; + color.getHslF(&h, &s, &l, &a); + + qreal alpha = a - amount; + if (alpha < 0) { + alpha = 0; + } + + return QColor::fromHslF(h, s, l, alpha); +} + +static bool isDarkMode() +{ + const QColor textColor = QGuiApplication::palette().color(QPalette::Text); + if (qSqrt(((textColor.red() * textColor.red()) * 0.299) + + ((textColor.green() * textColor.green()) * 0.587) + + ((textColor.blue() * textColor.blue()) * 0.114)) > 128) { + return true; + } + + return false; +} + +static QPalette paletteAdwaita() +{ + QPalette palette; + + // Colors defined in GTK adwaita style in _colors.scss + QColor base_color = QColor("white"); + QColor text_color = QColor("black"); + QColor bg_color = QColor("#f6f5f4"); + QColor fg_color = QColor("#2e3436"); + QColor selected_bg_color = QColor("#3584e4"); + QColor selected_fg_color = QColor("white"); + QColor osd_text_color = QColor("white"); + QColor osd_bg_color = QColor("black"); + QColor shadow = Colors::transparentize(QColor("black"), 0.9); + + QColor backdrop_fg_color = Colors::mix(fg_color, bg_color); + QColor backdrop_base_color = Colors::darken(base_color, 0.01); + QColor backdrop_selected_fg_color = backdrop_base_color; + + // This is the color we use as initial color for the gradient in normal state + // Defined in _drawing.scss button(normal) + QColor button_base_color = Colors::darken(bg_color, 0.04); + + QColor link_color = Colors::darken(selected_bg_color, 0.1); + QColor link_visited_color = Colors::darken(selected_bg_color, 0.2); + + palette.setColor(QPalette::All, QPalette::Window, bg_color); + palette.setColor(QPalette::All, QPalette::WindowText, fg_color); + palette.setColor(QPalette::All, QPalette::Base, base_color); + palette.setColor(QPalette::All, QPalette::AlternateBase, base_color); + palette.setColor(QPalette::All, QPalette::ToolTipBase, osd_bg_color); + palette.setColor(QPalette::All, QPalette::ToolTipText, osd_text_color); + palette.setColor(QPalette::All, QPalette::Text, fg_color); + palette.setColor(QPalette::All, QPalette::Button, button_base_color); + palette.setColor(QPalette::All, QPalette::ButtonText, fg_color); + palette.setColor(QPalette::All, QPalette::BrightText, text_color); + + palette.setColor(QPalette::All, QPalette::Light, Colors::lighten(button_base_color)); + palette.setColor(QPalette::All, QPalette::Midlight, Colors::mix(Colors::lighten(button_base_color), button_base_color)); + palette.setColor(QPalette::All, QPalette::Mid, Colors::mix(Colors::darken(button_base_color), button_base_color)); + palette.setColor(QPalette::All, QPalette::Dark, Colors::darken(button_base_color)); + palette.setColor(QPalette::All, QPalette::Shadow, shadow); + + palette.setColor(QPalette::All, QPalette::Highlight, selected_bg_color); + palette.setColor(QPalette::All, QPalette::HighlightedText, selected_fg_color); + + palette.setColor(QPalette::All, QPalette::Link, link_color); + palette.setColor(QPalette::All, QPalette::LinkVisited, link_visited_color); + + QColor insensitive_fg_color = Colors::mix(fg_color, bg_color); + QColor insensitive_bg_color = Colors::mix(bg_color, base_color, 0.4); + + palette.setColor(QPalette::Disabled, QPalette::Window, insensitive_bg_color); + palette.setColor(QPalette::Disabled, QPalette::WindowText, insensitive_fg_color); + palette.setColor(QPalette::Disabled, QPalette::Base, base_color); + palette.setColor(QPalette::Disabled, QPalette::AlternateBase, base_color); + palette.setColor(QPalette::Disabled, QPalette::Text, insensitive_fg_color); + palette.setColor(QPalette::Disabled, QPalette::Button, insensitive_bg_color); + palette.setColor(QPalette::Disabled, QPalette::ButtonText, insensitive_fg_color); + palette.setColor(QPalette::Disabled, QPalette::BrightText, text_color); + + palette.setColor(QPalette::Disabled, QPalette::Light, Colors::lighten(insensitive_bg_color)); + palette.setColor(QPalette::Disabled, QPalette::Midlight, Colors::mix(Colors::lighten(insensitive_bg_color), insensitive_bg_color)); + palette.setColor(QPalette::Disabled, QPalette::Mid, Colors::mix(Colors::darken(insensitive_bg_color), insensitive_bg_color)); + palette.setColor(QPalette::Disabled, QPalette::Dark, Colors::darken(insensitive_bg_color)); + palette.setColor(QPalette::Disabled, QPalette::Shadow, shadow); + + palette.setColor(QPalette::Disabled, QPalette::Highlight, selected_bg_color); + palette.setColor(QPalette::Disabled, QPalette::HighlightedText, selected_fg_color); + + palette.setColor(QPalette::Disabled, QPalette::Link, link_color); + palette.setColor(QPalette::Disabled, QPalette::LinkVisited, link_visited_color); + + + palette.setColor(QPalette::Inactive, QPalette::Window, bg_color); + palette.setColor(QPalette::Inactive, QPalette::WindowText, backdrop_fg_color); + palette.setColor(QPalette::Inactive, QPalette::Base, backdrop_base_color); + palette.setColor(QPalette::Inactive, QPalette::AlternateBase, backdrop_base_color); + palette.setColor(QPalette::Inactive, QPalette::Text, backdrop_fg_color); + palette.setColor(QPalette::Inactive, QPalette::Button, button_base_color); + palette.setColor(QPalette::Inactive, QPalette::ButtonText, backdrop_fg_color); + palette.setColor(QPalette::Inactive, QPalette::BrightText, text_color); + + palette.setColor(QPalette::Inactive, QPalette::Light, Colors::lighten(insensitive_bg_color)); + palette.setColor(QPalette::Inactive, QPalette::Midlight, Colors::mix(Colors::lighten(insensitive_bg_color), insensitive_bg_color)); + palette.setColor(QPalette::Inactive, QPalette::Mid, Colors::mix(Colors::darken(insensitive_bg_color), insensitive_bg_color)); + palette.setColor(QPalette::Inactive, QPalette::Dark, Colors::darken(insensitive_bg_color)); + palette.setColor(QPalette::Inactive, QPalette::Shadow, shadow); + + palette.setColor(QPalette::Inactive, QPalette::Highlight, selected_bg_color); + palette.setColor(QPalette::Inactive, QPalette::HighlightedText, backdrop_selected_fg_color); + + palette.setColor(QPalette::Inactive, QPalette::Link, link_color); + palette.setColor(QPalette::Inactive, QPalette::LinkVisited, link_visited_color); + + return palette; +} + +// private +static QPalette paletteAdwaitaDark() +{ + QPalette palette; + + // Colors defined in GTK adwaita style in _colors.scss + QColor base_color = Colors::lighten(Colors::desaturate(QColor("#241f31"), 1.0), 0.02); + QColor text_color = QColor("white"); + QColor bg_color = Colors::darken(Colors::desaturate(QColor("#3d3846"), 1.0), 0.04); + QColor fg_color = QColor("#eeeeec"); + QColor selected_bg_color = Colors::darken(QColor("#3584e4"), 0.2); + QColor selected_fg_color = QColor("white"); + QColor osd_text_color = QColor("white"); + QColor osd_bg_color = QColor("black"); + QColor shadow = Colors::transparentize(QColor("black"), 0.9); + + QColor backdrop_fg_color = Colors::mix(fg_color, bg_color); + QColor backdrop_base_color = Colors::lighten(base_color, 0.01); + QColor backdrop_selected_fg_color = Colors::mix(text_color, backdrop_base_color, 0.2); + + // This is the color we use as initial color for the gradient in normal state + // Defined in _drawing.scss button(normal) + QColor button_base_color = Colors::darken(bg_color, 0.01); + + QColor link_color = Colors::lighten(selected_bg_color, 0.2); + QColor link_visited_color = Colors::lighten(selected_bg_color, 0.1); + + palette.setColor(QPalette::All, QPalette::Window, bg_color); + palette.setColor(QPalette::All, QPalette::WindowText, fg_color); + palette.setColor(QPalette::All, QPalette::Base, base_color); + palette.setColor(QPalette::All, QPalette::AlternateBase, base_color); + palette.setColor(QPalette::All, QPalette::ToolTipBase, osd_bg_color); + palette.setColor(QPalette::All, QPalette::ToolTipText, osd_text_color); + palette.setColor(QPalette::All, QPalette::Text, fg_color); + palette.setColor(QPalette::All, QPalette::Button, button_base_color); + palette.setColor(QPalette::All, QPalette::ButtonText, fg_color); + palette.setColor(QPalette::All, QPalette::BrightText, text_color); + + palette.setColor(QPalette::All, QPalette::Light, Colors::lighten(button_base_color)); + palette.setColor(QPalette::All, QPalette::Midlight, Colors::mix(Colors::lighten(button_base_color), button_base_color)); + palette.setColor(QPalette::All, QPalette::Mid, Colors::mix(Colors::darken(button_base_color), button_base_color)); + palette.setColor(QPalette::All, QPalette::Dark, Colors::darken(button_base_color)); + palette.setColor(QPalette::All, QPalette::Shadow, shadow); + + palette.setColor(QPalette::All, QPalette::Highlight, selected_bg_color); + palette.setColor(QPalette::All, QPalette::HighlightedText, selected_fg_color); + + palette.setColor(QPalette::All, QPalette::Link, link_color); + palette.setColor(QPalette::All, QPalette::LinkVisited, link_visited_color); + + + QColor insensitive_fg_color = Colors::mix(fg_color, bg_color); + QColor insensitive_bg_color = Colors::mix(bg_color, base_color, 0.4); + + palette.setColor(QPalette::Disabled, QPalette::Window, insensitive_bg_color); + palette.setColor(QPalette::Disabled, QPalette::WindowText, insensitive_fg_color); + palette.setColor(QPalette::Disabled, QPalette::Base, base_color); + palette.setColor(QPalette::Disabled, QPalette::AlternateBase, base_color); + palette.setColor(QPalette::Disabled, QPalette::Text, insensitive_fg_color); + palette.setColor(QPalette::Disabled, QPalette::Button, insensitive_bg_color); + palette.setColor(QPalette::Disabled, QPalette::ButtonText, insensitive_fg_color); + palette.setColor(QPalette::Disabled, QPalette::BrightText, text_color); + + palette.setColor(QPalette::Disabled, QPalette::Light, Colors::lighten(insensitive_bg_color)); + palette.setColor(QPalette::Disabled, QPalette::Midlight, Colors::mix(Colors::lighten(insensitive_bg_color), insensitive_bg_color)); + palette.setColor(QPalette::Disabled, QPalette::Mid, Colors::mix(Colors::darken(insensitive_bg_color), insensitive_bg_color)); + palette.setColor(QPalette::Disabled, QPalette::Dark, Colors::darken(insensitive_bg_color)); + palette.setColor(QPalette::Disabled, QPalette::Shadow, shadow); + + palette.setColor(QPalette::Disabled, QPalette::Highlight, selected_bg_color); + palette.setColor(QPalette::Disabled, QPalette::HighlightedText, selected_fg_color); + + palette.setColor(QPalette::Disabled, QPalette::Link, link_color); + palette.setColor(QPalette::Disabled, QPalette::LinkVisited, link_visited_color); + + + palette.setColor(QPalette::Inactive, QPalette::Window, bg_color); + palette.setColor(QPalette::Inactive, QPalette::WindowText, backdrop_fg_color); + palette.setColor(QPalette::Inactive, QPalette::Base, backdrop_base_color); + palette.setColor(QPalette::Inactive, QPalette::AlternateBase, backdrop_base_color); + palette.setColor(QPalette::Inactive, QPalette::Text, backdrop_fg_color); + palette.setColor(QPalette::Inactive, QPalette::Button, button_base_color); + palette.setColor(QPalette::Inactive, QPalette::ButtonText, backdrop_fg_color); + palette.setColor(QPalette::Inactive, QPalette::BrightText, text_color); + + palette.setColor(QPalette::Inactive, QPalette::Light, Colors::lighten(insensitive_bg_color)); + palette.setColor(QPalette::Inactive, QPalette::Midlight, Colors::mix(Colors::lighten(insensitive_bg_color), insensitive_bg_color)); + palette.setColor(QPalette::Inactive, QPalette::Mid, Colors::mix(Colors::darken(insensitive_bg_color), insensitive_bg_color)); + palette.setColor(QPalette::Inactive, QPalette::Dark, Colors::darken(insensitive_bg_color)); + palette.setColor(QPalette::Inactive, QPalette::Shadow, shadow); + + palette.setColor(QPalette::Inactive, QPalette::Highlight, selected_bg_color); + palette.setColor(QPalette::Inactive, QPalette::HighlightedText, backdrop_selected_fg_color); + + palette.setColor(QPalette::Inactive, QPalette::Link, link_color); + palette.setColor(QPalette::Inactive, QPalette::LinkVisited, link_visited_color); + + return palette; +} + +// private +static QPalette paletteAdwaitaHighContrast() +{ + QPalette palette; + + return palette; +} + +QPalette Colors::disabledPalette(const QPalette &source, qreal ratio) +{ + QPalette copy(source); + + const QList roles = { QPalette::Background, QPalette::Highlight, QPalette::WindowText, QPalette::ButtonText, QPalette::Text, QPalette::Button }; + foreach (const QPalette::ColorRole &role, roles) { + copy.setColor(role, Colors::mix(source.color(QPalette::Active, role), source.color(QPalette::Disabled, role), 1.0 - ratio)); + } + + return copy; +} + +QPalette Colors::palette(ColorVariant variant) +{ + if (variant == ColorVariant::Unknown) { + return isDarkMode() ? paletteAdwaitaDark() : paletteAdwaita(); + } else if (variant == ColorVariant::Adwaita) { + return paletteAdwaita(); + } else if (variant == ColorVariant::AdwaitaDark) { + return paletteAdwaitaDark(); + } else { + // TODO + return paletteAdwaitaHighContrast(); + } +} + +QColor Colors::hoverColor(const StyleOptions &options) +{ + return options.palette().highlight().color(); +} + +QColor Colors::focusColor(const StyleOptions &options) +{ + return options.palette().highlight().color(); +} + +QColor Colors::negativeText(const StyleOptions &options) +{ + Q_UNUSED(options) + + return Qt::red; +} + +QColor Colors::shadowColor(const StyleOptions &options) +{ + return alphaColor(options.palette().color(QPalette::Shadow), 0.15); +} + +QColor Colors::titleBarColor(const StyleOptions &options) +{ + return options.palette().color(options.active() ? QPalette::Active : QPalette::Inactive, QPalette::Window); +} + +QColor Colors::titleBarTextColor(const StyleOptions &options) +{ + return options.palette().color(options.active() ? QPalette::Active : QPalette::Inactive, QPalette::WindowText); +} + +QColor Colors::arrowOutlineColor(const StyleOptions &options) +{ + switch (options.colorRole()) { + case QPalette::Text: + return mix(options.palette().color(options.colorGroup(), QPalette::Text), options.palette().color(options.colorGroup(), QPalette::Base), arrowShade); + case QPalette::WindowText: + return mix(options.palette().color(options.colorGroup(), QPalette::WindowText), options.palette().color(options.colorGroup(), QPalette::Window), arrowShade); + case QPalette::ButtonText: + return mix(options.palette().color(options.colorGroup(), QPalette::ButtonText), options.palette().color(options.colorGroup(), QPalette::Button), arrowShade); + default: + return options.palette().text().color(); + } +} + +QColor Colors::buttonOutlineColor(const StyleOptions &options) +{ + ColorVariant variant = options.colorVariant(); + + if (variant == ColorVariant::Unknown) { + variant = isDarkMode() ? ColorVariant::AdwaitaDark : ColorVariant::Adwaita; + } + + if (variant == ColorVariant::AdwaitaDark) { + return darken(options.palette().color(QPalette::Window), 0.1); + } else { + return darken(options.palette().color(QPalette::Window), 0.18); + } +} + +QColor Colors::indicatorOutlineColor(const StyleOptions &options) +{ + bool isDisabled = options.palette().currentColorGroup() == QPalette::Disabled; + if (options.inMenu() || options.checkboxState() == CheckBoxState::CheckOff) { + ColorVariant variant = options.colorVariant(); + + if (variant == ColorVariant::Unknown) { + variant = isDarkMode() ? ColorVariant::AdwaitaDark : ColorVariant::Adwaita; + } + + if (isDisabled) { + return buttonOutlineColor(options); + } + + if (variant == ColorVariant::AdwaitaDark) { + return darken(options.palette().color(QPalette::Window), 0.18); + } else { + return darken(options.palette().color(QPalette::Window), 0.24); + } + } else { + return options.palette().color(QPalette::Highlight); + } +} + +QColor Colors::frameOutlineColor(const StyleOptions &options) +{ + return inputOutlineColor(options); +} + +QColor Colors::inputOutlineColor(const StyleOptions &options) +{ + QColor outline(buttonOutlineColor(options)); + + // focus takes precedence over hover + if (options.animationMode() == AnimationFocus) { + outline = mix(outline, focusColor(options)); + } else if (options.hasFocus()) { + outline = focusColor(options); + } + + return outline; +} + +QColor Colors::sidePanelOutlineColor(const StyleOptions &options) +{ + QColor outline(options.palette().color(QPalette::Inactive, QPalette::Highlight)); + QColor focus(options.palette().color(QPalette::Active, QPalette::Highlight)); + + if (options.animationMode() == AnimationFocus) { + outline = mix(outline, focus, options.opacity()); + } else if (options.hasFocus()) { + outline = focus; + } + + return outline; +} + +QColor Colors::sliderOutlineColor(const StyleOptions &options) +{ + QColor outline(mix(options.palette().color(QPalette::Window), options.palette().color(QPalette::Shadow), 0.5)); + + // hover takes precedence over focus + if (options.animationMode() == AnimationHover) { + QColor hover(hoverColor(options)); + QColor focus(focusColor(options)); + if (options.hasFocus()) { + outline = mix(focus, hover, options.opacity()); + } else { + outline = mix(outline, hover, options.opacity()); + } + } else if (options.mouseOver()) { + outline = hoverColor(options); + } else if (options.animationMode() == AnimationFocus) { + QColor focus(focusColor(options)); + outline = mix(outline, focus, options.opacity()); + } else if (options.hasFocus()) { + outline = focusColor(options); + } + + return outline; +} + +QColor Colors::buttonBackgroundColor(const StyleOptions &options) +{ + bool isDisabled = options.palette().currentColorGroup() == QPalette::Disabled; + QColor buttonBackground(options.palette().color(QPalette::Button)); + QColor background(options.palette().color(QPalette::Window)); + + ColorVariant variant = options.colorVariant(); + + if (variant == ColorVariant::Unknown) { + variant = isDarkMode() ? ColorVariant::AdwaitaDark : ColorVariant::Adwaita; + } + + const bool darkMode = variant == ColorVariant::AdwaitaDark; + const QPalette &palette = options.palette(); + + if (isDisabled && (options.animationMode() == AnimationPressed || options.sunken())) { + // Defined in drawing.css - insensitive-active button + // if($variant == 'light', Colors::darken(Colors::mix($c, $base_color, 85%), 8%), Colors::darken(Colors::mix($c, $base_color, 85%), 6%)); + // FIXME: doesn't seem to be correct color + return darkMode ? Colors::darken(Colors::mix(palette.color(QPalette::Active, QPalette::Window), palette.color(QPalette::Active, QPalette::Base), 0.15), 0.06) : + Colors::darken(Colors::mix(palette.color(QPalette::Active, QPalette::Window), palette.color(QPalette::Active, QPalette::Base), 0.15), 0.08); + } + + if (options.animationMode() == AnimationPressed) { + if (darkMode) { + // Active button for dark mode is Colors::darken(bg_color, 0.09) + return Colors::mix(Colors::darken(background, 0.01), Colors::darken(background, 0.09), options.opacity()); + } else { + // Active button for normal mode is Colors::darken(bg_color, 0.14) + return Colors::mix(buttonBackground, Colors::darken(background, 0.14), options.opacity()); + } + } else if (options.sunken()) { + if (darkMode) { + // Active button for dark mode is Colors::darken(bg_color, 0.09) + return Colors::darken(background, 0.09); + } else { + // Active button for normal mode is Colors::darken(bg_color, 0.14) + return Colors::darken(background, 0.14); + } + } else if (options.animationMode() == AnimationHover) { + if (darkMode) { + // Hovered button for dark mode is Colors::darken(bg_color, 0.01) + return Colors::mix(buttonBackground, Colors::darken(background, 0.01), options.opacity()); + } else { + // Hovered button for normal mode is bg_color + return Colors::mix(buttonBackground, background, options.opacity()); + } + } else if (options.mouseOver()) { + if (darkMode) { + // Hovered button for dark mode is Colors::darken(bg_color, 0.01) + return Colors::darken(background, 0.01); + } else { + // Hovered button for normal mode is bg_color + return background; + } + } + + return buttonBackground; +} + +QColor Colors::checkBoxIndicatorColor(const StyleOptions &options) +{ + if (options.inMenu()) { + return options.palette().color(QPalette::Text); + } else { + if (options.active()) { + return options.palette().color(QPalette::HighlightedText); + } else { + return Colors::transparentize(options.palette().color(QPalette::ToolTipText), 0.2); + } + } +} + +QColor Colors::headerTextColor(const StyleOptions &options) +{ + QColor col(options.palette().color(QPalette::WindowText)); + + if (options.state() & QStyle::State_Enabled) { + if (options.state() & QStyle::State_Sunken) { + return Colors::alphaColor(col, 0.9); + } else if (options.state() & QStyle::State_MouseOver) { + return Colors::alphaColor(col, 0.7); + } + } + return Colors::alphaColor(col, 0.5); +} + +QColor Colors::indicatorBackgroundColor(const StyleOptions &options) +{ + ColorVariant variant = options.colorVariant(); + + if (variant == ColorVariant::Unknown) { + variant = isDarkMode() ? ColorVariant::AdwaitaDark : ColorVariant::Adwaita; + } + + const QPalette &palette = options.palette(); + const bool darkMode = variant == ColorVariant::AdwaitaDark; + + bool isDisabled = palette.currentColorGroup() == QPalette::Disabled; + QColor background(palette.color(QPalette::Window)); + // Normal-alt button for dark mode is Colors::darken(bg_color, 0.03) + // Normal-alt button for normal mode is Colors::lighten(bg_color, 0.05) + QColor indicatorColor(darkMode ? Colors::darken(background, 0.03) : Colors::lighten(background, 0.05)); + + if (options.inMenu() || options.checkboxState() == CheckOff) { + if (isDisabled) { + // Defined in drawing.css - insensitive button + // $insensitive_bg_color: Colors::mix($bg_color, $base_color, 60%); + return Colors::mix(palette.color(QPalette::Active, QPalette::Window), palette.color(QPalette::Active, QPalette::Base), 0.6); + } + + if (options.animationMode() == AnimationPressed) { + if (darkMode) { + // Active button for dark mode is Colors::darken(bg_color, 0.09) + return Colors::mix(background, Colors::darken(background, 0.09), options.opacity()); + } else { + // Active button for normal mode is Colors::darken(bg_color, 0.14) + return Colors::mix(Colors::lighten(background, 0.0), Colors::darken(background, 0.14), options.opacity()); + } + } else if (options.sunken()) { + if (darkMode) { + // Active button for dark mode is Colors::darken(bg_color, 0.09) + return Colors::darken(background, 0.09); + } else { + // Active button for normal mode is Colors::darken(bg_color, 0.14) + return Colors::darken(background, 0.14); + } + } else if (options.animationMode() == AnimationHover) { + if (darkMode) { + // Hovered-alt button for dark mode is bg_color + return Colors::mix(indicatorColor, background, options.opacity()); + } else { + // Hovered-alt button for normal mode is Colors::lighten(bg_color, 0.09) + return Colors::mix(indicatorColor, Colors::lighten(background, 0.09), options.opacity()); + } + } else if (options.mouseOver()) { + if (darkMode) { + // Hovered-alt button for dark mode is bg_color + return background; + } else { + // Hovered-alt button for normal mode is Colors::lighten(bg_color, 0.09) + return Colors::lighten(background, 0.09); + } + } + } else { + if (darkMode) { + return Colors::lighten(palette.color(QPalette::Highlight)); + } else { + return palette.color(QPalette::Highlight); + } + } + + return indicatorColor; +} + +QColor Colors::frameBackgroundColor(const StyleOptions &options) +{ + return Colors::mix(options.palette().color(options.colorGroup(), QPalette::Window), options.palette().color(options.colorGroup(), QPalette::Base), 0.3); +} + +QColor Colors::scrollBarHandleColor(const StyleOptions &options) +{ + ColorVariant variant = options.colorVariant(); + + if (variant == ColorVariant::Unknown) { + variant = isDarkMode() ? ColorVariant::AdwaitaDark : ColorVariant::Adwaita; + } + + QColor fgColor = options.palette().color(QPalette::Text); + QColor bgColor = options.palette().color(QPalette::Window); + QColor selectedBgColor = options.palette().color(QPalette::Highlight); + + QColor color(Colors::mix(fgColor, bgColor, 0.4)); + QColor hoverColor(Colors::mix(fgColor, bgColor, 0.2)); + QColor activeColor(variant == ColorVariant::AdwaitaDark ? Colors::lighten(selectedBgColor, 0.1) : Colors::darken(selectedBgColor, 0.1)); + + // hover takes precedence over focus + if (options.animationMode() == AnimationPressed) { + if (options.mouseOver()) { + color = Colors::mix(hoverColor, activeColor, options.opacity()); + } else { + color = Colors::mix(color, activeColor, options.opacity()); + } + } else if (options.sunken()) { + color = activeColor; + } else if (options.animationMode() == AnimationHover) { + color = Colors::mix(color, hoverColor, options.opacity()); + } else if (options.mouseOver()) { + color = hoverColor; + } + + return color; +} + +QColor Colors::separatorColor(const StyleOptions &options) +{ + return buttonOutlineColor(options); +} + +QColor Colors::toolButtonColor(const StyleOptions &options) +{ + if (options.sunken() || (options.animationMode() != AnimationNone && options.animationMode() != AnimationHover)) { + return buttonBackgroundColor(options); + } + + return Qt::transparent; +} + +QColor Colors::tabBarColor(const StyleOptions &options) +{ + QColor background(Colors::mix(options.palette().window().color(), options.palette().shadow().color(), 0.15)); + if (!(options.state() & QStyle::State_Enabled)) { + background = background.lighter(115); + } + + if (!(options.state() & QStyle::State_Active)) { + background = background.lighter(115); + } + return background; +} + +} // namespace Adwaita diff --git a/src/lib/adwaitacolors.h b/src/lib/adwaitacolors.h new file mode 100644 index 0000000..d0f55a8 --- /dev/null +++ b/src/lib/adwaitacolors.h @@ -0,0 +1,214 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * Copyright (C) 2014-2018 Martin Bříza * + * Copyright (C) 2019-2020 Jan Grulich * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_COLORS_H +#define ADWAITA_COLORS_H + +#include "adwaita.h" +#include "adwaitaqt_export.h" + +#include + +namespace Adwaita +{ + +class ADWAITAQT_EXPORT Colors +{ +public: + // Color adjustments + static QColor alphaColor(QColor color, qreal alpha); + static QColor darken(const QColor &color, qreal amount = 0.1); + static QColor desaturate(const QColor &color, qreal amount = 0.1); + static QColor lighten(const QColor &color, qreal amount = 0.1); + static QColor mix(const QColor &c1, const QColor &c2, qreal bias = 0.5); + static QColor transparentize(const QColor &color, qreal amount = 0.1); + + static QPalette disabledPalette(const QPalette &source, qreal ratio); + // Color palette for each Adwaita variant + static QPalette palette(ColorVariant variant = ColorVariant::Unknown); + + // Generic colors + + /* Relevant options: + * palette + */ + static QColor hoverColor(const StyleOptions &options); + /* Relevant options: + * palette + */ + static QColor focusColor(const StyleOptions &options); + /* Relevant options: + * palette + */ + static QColor negativeText(const StyleOptions &options); + /* Relevant options: + * palette + */ + static QColor shadowColor(const StyleOptions &options); + /* Relevant options: + * palette + * active + */ + static QColor titleBarColor(const StyleOptions &options); + /* Relevant options: + * palette + * active + */ + static QColor titleBarTextColor(const StyleOptions &options); + + // Outline colors + + /* Relevant options: + * palette + * color role + * color group + */ + static QColor arrowOutlineColor(const StyleOptions &options); + /* Relevant options: + * palette + * mouse over + * has focus + * opacity + * animation mode + * color variant + */ + static QColor buttonOutlineColor(const StyleOptions &options); + /* Relevant options: + * palette + * mouse over + * has focus + * opacity + * animation mode + * checkbox state + * color variant + * in menu + */ + static QColor indicatorOutlineColor(const StyleOptions &options); + /* Relevant options: + * palette + * mouse over + * has focus + * opacity + * animation mode + * color variant + */ + static QColor frameOutlineColor(const StyleOptions &options); + /* Relevant options: + * palette + * mouse over + * has focus + * opacity + * animation mode + * color variant + */ + static QColor inputOutlineColor(const StyleOptions &options); + /* Relevant options: + * palette + * has focus + * opacity + * animation mode + */ + static QColor sidePanelOutlineColor(const StyleOptions &options); + /* Relevant options: + * palette + * mouse over + * has focus + * opacity + * animation mode + */ + static QColor sliderOutlineColor(const StyleOptions &options); + + // Background colors + + /* Relevant options: + * palette + * mouse over + * has focus + * sunken + * opacity + * animation mode + * color variant + */ + static QColor buttonBackgroundColor(const StyleOptions &options); + /* Relevant options: + * palette + * mouse over + * active + * opacity + * animation mode + * color variant + */ + static QColor checkBoxIndicatorColor(const StyleOptions &options); + /* Relevant options: + * palette + * state + */ + static QColor headerTextColor(const StyleOptions &options); + /* Relevant options: + * palette + * mouse over + * has focus + * sunken + * opacity + * animation mode + * checkbox state + * color variant + * in menu + */ + static QColor indicatorBackgroundColor(const StyleOptions &options); + /* Relevant options: + * palette + */ + static QColor frameBackgroundColor(const StyleOptions &options); + /* Relevant options: + * palette + * mouse over + * has focus + * sunken + * opacity + * animation mode + * color variant + */ + static QColor scrollBarHandleColor(const StyleOptions &options); + /* Relevant options: + * palette + * color variant + */ + static QColor separatorColor(const StyleOptions &options); + /* Relevant options: + * palette + * mouse over + * has focus + * sunken + * opacity + * animation mode + */ + static QColor toolButtonColor(const StyleOptions &options); + /* Relevant options: + * palette + */ + static QColor tabBarColor(const StyleOptions &options); +}; + +} // namespace Adwaita + +#endif // ADWAITA_COLORS_H + diff --git a/src/lib/adwaitadebug.h b/src/lib/adwaitadebug.h new file mode 100644 index 0000000..1179f45 --- /dev/null +++ b/src/lib/adwaitadebug.h @@ -0,0 +1,27 @@ +/************************************************************************* + * Copyright (C) 2019 Jan Grulich * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_DEBUG_H +#define ADWAITA_DEBUG_H + +#include + +Q_DECLARE_LOGGING_CATEGORY(ADWAITA) + +#endif // ADWAITA_DEBUG_H diff --git a/src/lib/adwaitamnemonics.cpp b/src/lib/adwaitamnemonics.cpp new file mode 100644 index 0000000..34d1b9e --- /dev/null +++ b/src/lib/adwaitamnemonics.cpp @@ -0,0 +1,92 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitamnemonics.h" +#include "adwaita.h" + +#include +#include + +namespace Adwaita +{ + +//____________________________________________________ +void Mnemonics::setMode(int mode) +{ + switch (mode) { + case Adwaita::MN_NEVER: + qApp->removeEventFilter(this); + setEnabled(false); + break; + case Adwaita::MN_ALWAYS: + qApp->removeEventFilter(this); + setEnabled(true); + break; + case Adwaita::MN_AUTO: + qApp->removeEventFilter(this); + qApp->installEventFilter(this); + setEnabled(false); + break; + default: + break; + } + + return; +} + +//____________________________________________________ +bool Mnemonics::eventFilter(QObject *, QEvent *event) +{ + switch (event->type()) { + case QEvent::KeyPress: + if (static_cast(event)->key() == Qt::Key_Alt) { + setEnabled(true); + } + break; + case QEvent::KeyRelease: + if (static_cast(event)->key() == Qt::Key_Alt) { + setEnabled(false); + } + break; + case QEvent::ApplicationStateChange: + setEnabled(false); + break; + default: + break; + } + + return false; +} + +//____________________________________________________ +void Mnemonics::setEnabled(bool value) +{ + if (_enabled == value) { + return; + } + + _enabled = value; + + // update all top level widgets + foreach (QWidget *widget, qApp->topLevelWidgets()) { + widget->update(); + } +} + +} // namespace Adwaita diff --git a/src/lib/adwaitamnemonics.h b/src/lib/adwaitamnemonics.h new file mode 100644 index 0000000..0447f82 --- /dev/null +++ b/src/lib/adwaitamnemonics.h @@ -0,0 +1,76 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_MNEMONICS_H +#define ADWAITA_MNEMONICS_H + +#include "adwaitaqt_export.h" + +#include +#include +#include + +namespace Adwaita +{ +class ADWAITAQT_EXPORT Mnemonics : public QObject +{ + Q_OBJECT +public: + //* constructor + explicit Mnemonics(QObject *parent) + : QObject(parent) + , _enabled(true) + { + } + + //* destructor + virtual ~Mnemonics() + { + } + + //* set mode + void setMode(int); + + //* event filter + virtual bool eventFilter(QObject *, QEvent *); + + //* true if mnemonics are enabled + const bool &enabled() const + { + return _enabled; + } + + //* alignment flag + int textFlags() const + { + return _enabled ? Qt::TextShowMnemonic : Qt::TextHideMnemonic; + } + +protected: + //* set enable state + void setEnabled(bool); + +private: + //* enable state + bool _enabled; +}; + +} // namespace Adwaita + +#endif // ADWAITA_MNEMONICS_H diff --git a/src/lib/adwaitaqt_export.h b/src/lib/adwaitaqt_export.h new file mode 100644 index 0000000..7e2e165 --- /dev/null +++ b/src/lib/adwaitaqt_export.h @@ -0,0 +1,28 @@ +#ifndef ADWAITAQT_EXPORT_H +#define ADWAITAQT_EXPORT_H + +#if defined(_WIN32) +# define ADWAITAQT_EXPORT __declspec(dllexport) +#elif defined(__GNUC__) +# define ADWAITAQT_EXPORT __attribute__((visibility("default"))) +#else +# define ADWAITAQT_EXPORT +#endif + +#ifndef ADWAITAQT_NO_EXPORT +# define ADWAITAQT_NO_EXPORT __attribute__((visibility("hidden"))) +#endif + +#ifndef ADWAITAQT_DEPRECATED +# define ADWAITAQT_DEPRECATED __attribute__ ((__deprecated__)) +#endif + +#ifndef ADWAITAQT_DEPRECATED_EXPORT +# define ADWAITAQT_DEPRECATED_EXPORT ADWAITAQT_EXPORT ADWAITAQT_DEPRECATED +#endif + +#ifndef ADWAITAQT_DEPRECATED_NO_EXPORT +# define ADWAITAQT_DEPRECATED_NO_EXPORT ADWAITAQT_NO_EXPORT ADWAITAQT_DEPRECATED +#endif + +#endif diff --git a/src/lib/adwaitasplitterproxy.cpp b/src/lib/adwaitasplitterproxy.cpp new file mode 100644 index 0000000..6afaa6c --- /dev/null +++ b/src/lib/adwaitasplitterproxy.cpp @@ -0,0 +1,319 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitasplitterproxy.h" + +#include "adwaita.h" + +#include +#include +#include + +namespace Adwaita +{ + +//____________________________________________________________________ +void SplitterFactory::setEnabled(bool value) +{ + if (_enabled != value) { + // store + _enabled = value; + + // assign to existing splitters + for (WidgetMap::iterator iter = _widgets.begin(); iter != _widgets.end(); ++iter) { + if (iter.value()) iter.value().data()->setEnabled(value); + } + } +} + +//____________________________________________________________________ +bool SplitterFactory::registerWidget(QWidget *widget) +{ + // check widget type + if (qobject_cast(widget)) { + WidgetMap::iterator iter(_widgets.find(widget)); + if (iter == _widgets.end() || !iter.value()) { + widget->installEventFilter(&_addEventFilter); + SplitterProxy *proxy(new SplitterProxy(widget, _enabled)); + widget->removeEventFilter(&_addEventFilter); + + widget->installEventFilter(proxy); + _widgets.insert(widget, proxy); + } else { + widget->removeEventFilter(iter.value().data()); + widget->installEventFilter(iter.value().data()); + } + + return true; + } else if (qobject_cast(widget)) { + QWidget *window(widget->window()); + WidgetMap::iterator iter(_widgets.find(window)); + if (iter == _widgets.end() || !iter.value()) { + window->installEventFilter(&_addEventFilter); + SplitterProxy *proxy(new SplitterProxy(window, _enabled)); + window->removeEventFilter(&_addEventFilter); + + widget->installEventFilter(proxy); + _widgets.insert(window, proxy); + } else { + widget->removeEventFilter(iter.value().data()); + widget->installEventFilter(iter.value().data()); + } + + return true; + } else { + return false; + } +} + +//____________________________________________________________________ +void SplitterFactory::unregisterWidget(QWidget *widget) +{ + WidgetMap::iterator iter(_widgets.find(widget)); + if (iter != _widgets.end()) { + if (iter.value()) iter.value().data()->deleteLater(); + _widgets.erase(iter); + } +} + +//____________________________________________________________________ +SplitterProxy::SplitterProxy(QWidget *parent, bool enabled): + QWidget(parent), + _enabled(enabled), + _timerId(0) +{ + setAttribute(Qt::WA_TranslucentBackground, true); + setAttribute(Qt::WA_OpaquePaintEvent, false); + hide(); +} + +//____________________________________________________________________ +SplitterProxy::~SplitterProxy(void) +{} + +//____________________________________________________________________ +void SplitterProxy::setEnabled(bool value) +{ + // make sure status has changed + if (_enabled != value) { + _enabled = value; + if (_enabled) clearSplitter(); + } +} + +//____________________________________________________________________ +bool SplitterProxy::eventFilter(QObject *object, QEvent *event) +{ + // do nothing if disabled + if (!_enabled) { + return false; + } + + // do nothing in case of mouse grab + if (mouseGrabber()) { + return false; + } + + switch (event->type()) { + case QEvent::HoverEnter: + if (!isVisible()) { + // cast to splitter handle + if (QSplitterHandle *handle = qobject_cast(object)) { + setSplitter(handle); + } + } + + return false; + case QEvent::HoverMove: + case QEvent::HoverLeave: + return isVisible() && object == _splitter.data(); + case QEvent::MouseMove: + case QEvent::Timer: + case QEvent::Move: + return false; + case QEvent::CursorChange: + if (QWidget *window = qobject_cast(object)) { + if (window->cursor().shape() == Qt::SplitHCursor || window->cursor().shape() == Qt::SplitVCursor) { + setSplitter(window); + } + } + return false; + case QEvent::WindowDeactivate: + case QEvent::MouseButtonRelease: + clearSplitter(); + return false; + default: + return false; + } +} + +//____________________________________________________________________ +bool SplitterProxy::event(QEvent *event) +{ + switch (event->type()) { +#if 0 + case QEvent::Paint: { + QPainter painter(this); + painter.setClipRegion(static_cast(event)->region()); + painter.setRenderHints(QPainter::Antialiasing); + painter.setPen(Qt::red); + painter.drawRect(QRectF(rect()).adjusted(0.5, 0.5, -0.5, -0.5)); + return true; + } +#endif + case QEvent::MouseMove: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: { + // check splitter + if (!_splitter) { + return false; + } + event->accept(); + + // grab on mouse press + if (event->type() == QEvent::MouseButtonPress) { + grabMouse(); + resize(1, 1); + } + + // cast to mouse event + QMouseEvent *mouseEvent(static_cast(event)); + + // get relevant position to post mouse drag event to application + if (event->type() == QEvent::MouseButtonPress) { + // use hook, to make sure splitter is properly dragged + QMouseEvent copy( + mouseEvent->type(), + _hook, + _splitter.data()->mapToGlobal(_hook), + mouseEvent->button(), + mouseEvent->buttons(), mouseEvent->modifiers()); + + QCoreApplication::sendEvent(_splitter.data(), ©); + } else { + // map event position to current splitter and post. + QMouseEvent copy( + mouseEvent->type(), + _splitter.data()->mapFromGlobal(mouseEvent->globalPos()), + mouseEvent->globalPos(), + mouseEvent->button(), + mouseEvent->buttons(), mouseEvent->modifiers()); + + QCoreApplication::sendEvent(_splitter.data(), ©); + } + + // release grab on mouse-Release + if (event->type() == QEvent::MouseButtonRelease && mouseGrabber() == this) { + releaseMouse(); + } + + return true; + } + case QEvent::Timer: + if (static_cast(event)->timerId() != _timerId) { + return QWidget::event(event); + } + /* + Fall through is intended. + We somehow lost a QEvent::Leave before timeout. We fix it from here + */ + + case QEvent::HoverLeave: + case QEvent::Leave: { + if (mouseGrabber() == this) { + return true; + } + // reset splitter + if (isVisible() && !rect().contains(mapFromGlobal(QCursor::pos()))) { + clearSplitter(); + } + return true; + } + default: + return QWidget::event(event); + + } +} + +//____________________________________________________________________ +void SplitterProxy::setSplitter(QWidget *widget) +{ + // check if changed + if (_splitter.data() == widget) { + return; + } + + // get cursor position + QPoint position(QCursor::pos()); + + // store splitter and hook + _splitter = widget; + _hook = _splitter.data()->mapFromGlobal(position); + + // adjust rect + QRect rect(0, 0, 2 * Adwaita::Config::SplitterProxyWidth, 2 * Adwaita::Config::SplitterProxyWidth); + rect.moveCenter(parentWidget()->mapFromGlobal(position)); + setGeometry(rect); + setCursor(_splitter.data()->cursor().shape()); + + // show + raise(); + show(); + + // timer used to automatically hide proxy in case leave events are lost + if (!_timerId) { + _timerId = startTimer(150); + } +} + +//____________________________________________________________________ +void SplitterProxy::clearSplitter(void) +{ + // check if changed + if (!_splitter) { + return; + } + + // release mouse + if (mouseGrabber() == this) { + releaseMouse(); + } + + // hide + parentWidget()->setUpdatesEnabled(false); + hide(); + parentWidget()->setUpdatesEnabled(true); + + // send hover event + if (_splitter) { + QHoverEvent hoverEvent( + qobject_cast(_splitter.data()) ? QEvent::HoverLeave : QEvent::HoverMove, + _splitter.data()->mapFromGlobal(QCursor::pos()), _hook); + QCoreApplication::sendEvent(_splitter.data(), &hoverEvent); + _splitter.clear(); + } + + // kill timer if any + if (_timerId) { + killTimer(_timerId); + _timerId = 0; + } +} + +} // namespace Adwaita diff --git a/src/lib/adwaitasplitterproxy.h b/src/lib/adwaitasplitterproxy.h new file mode 100644 index 0000000..d119f36 --- /dev/null +++ b/src/lib/adwaitasplitterproxy.h @@ -0,0 +1,131 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_SPLITTER_PROXY_H +#define ADWAITA_SPLITTER_PROXY_H + +#include "adwaita.h" +#include "adwaitaaddeventfilter.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace Adwaita +{ +class SplitterProxy; + +//* factory +class ADWAITAQT_EXPORT SplitterFactory : public QObject +{ + Q_OBJECT + +public: + //* constructor + explicit SplitterFactory(QObject *parent) + : QObject(parent) + , _enabled(false) + { + } + + //* destructor + virtual ~SplitterFactory() + { + } + + //* enabled state + void setEnabled(bool); + + //* register widget + bool registerWidget(QWidget *); + + //* unregister widget + void unregisterWidget(QWidget *); + +private: + //* enabled state + bool _enabled; + + //* needed to block ChildAdded events when creating proxy + AddEventFilter _addEventFilter; + + //* pointer to SplitterProxy + using SplitterProxyPointer = WeakPointer; + + //* registered widgets + using WidgetMap = QMap; + WidgetMap _widgets; +}; + +//* splitter 'proxy' widget, with extended hit area +class SplitterProxy : public QWidget +{ + Q_OBJECT + +public: + //* constructor + explicit SplitterProxy(QWidget *, bool = false); + + //* destructor + virtual ~SplitterProxy(); + + //* event filter + virtual bool eventFilter(QObject *, QEvent *); + + //* enable state + void setEnabled(bool); + + //* enable state + bool enabled() const + { + return _enabled; + } + +protected: + //* event handler + virtual bool event(QEvent *); + +protected: + //* reset 'true' splitter widget + void clearSplitter(); + + //* keep track of 'true' splitter widget + void setSplitter(QWidget *); + +private: + //* enabled state + bool _enabled; + + //* splitter object + WeakPointer _splitter; + + //* hook + QPoint _hook; + + //* timer id + int _timerId; +}; + +} // namespace Adwaita + +#endif // ADWAITA_SPLITTER_PROXY_H diff --git a/src/lib/adwaitawidgetexplorer.cpp b/src/lib/adwaitawidgetexplorer.cpp new file mode 100644 index 0000000..ee5bac0 --- /dev/null +++ b/src/lib/adwaitawidgetexplorer.cpp @@ -0,0 +1,172 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * Copyright (C) 2019 Jan Grulich * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitawidgetexplorer.h" + +#include "adwaita.h" +#include "adwaitadebug.h" + +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(ADWAITA, "adwaita.widgetexplorer") + +namespace Adwaita +{ + +//________________________________________________ +WidgetExplorer::WidgetExplorer(QObject *parent) + : QObject(parent) + , _enabled(false) + , _drawWidgetRects(false) +{ + _eventTypes.insert(QEvent::Enter, QStringLiteral("Enter")); + _eventTypes.insert(QEvent::Leave, QStringLiteral("Leave")); + + _eventTypes.insert(QEvent::HoverMove, QStringLiteral("HoverMove")); + _eventTypes.insert(QEvent::HoverEnter, QStringLiteral("HoverEnter")); + _eventTypes.insert(QEvent::HoverLeave, QStringLiteral("HoverLeave")); + + _eventTypes.insert(QEvent::MouseMove, QStringLiteral("MouseMove")); + _eventTypes.insert(QEvent::MouseButtonPress, QStringLiteral("MouseButtonPress")); + _eventTypes.insert(QEvent::MouseButtonRelease, QStringLiteral("MouseButtonRelease")); + + _eventTypes.insert(QEvent::FocusIn, QStringLiteral("FocusIn")); + _eventTypes.insert(QEvent::FocusOut, QStringLiteral("FocusOut")); + + // _eventTypes.insert( QEvent::Paint, "Paint" ); +} + +//________________________________________________ +void WidgetExplorer::setEnabled(bool value) +{ + if (value == _enabled) { + return; + } + + _enabled = value; + + qApp->removeEventFilter(this); + if (_enabled) { + qApp->installEventFilter(this); + } +} + +//________________________________________________ +bool WidgetExplorer::eventFilter(QObject *object, QEvent *event) +{ + +// if( object->isWidgetType() ) +// { +// QString type( _eventTypes[event->type()] ); +// if( !type.isEmpty() ) +// { +// QTextStream( stdout ) << "Adwaita::WidgetExplorer::eventFilter - widget: " << object << " (" << object->metaObject()->className() << ")"; +// QTextStream( stdout ) << " type: " << type << endl; +// } +// } + + switch (event->type()) { + case QEvent::Paint: + if (_drawWidgetRects) { + QWidget *widget(qobject_cast(object)); + if (!widget) + return false; + + QPainter painter(widget); + painter.setRenderHints(QPainter::Antialiasing); + painter.setBrush(Qt::NoBrush); + painter.setPen(Qt::red); + painter.drawRect(widget->rect()); + painter.end(); + } + break; + + case QEvent::MouseButtonPress: { + // cast event and check button + QMouseEvent *mouseEvent(static_cast(event)); + if (mouseEvent->button() != Qt::LeftButton) { + break; + } + + // case widget and check (should not be necessary) + QWidget *widget(qobject_cast(object)); + if (!widget) { + return false; + } + + qCDebug(ADWAITA) + << "Adwaita::WidgetExplorer::eventFilter -" + << " event: " << event << " type: " << eventType(event->type()) + << " widget: " << widgetInformation(widget); + + // print parent information + QWidget *parent(widget->parentWidget()); + while (parent) { + qCDebug(ADWAITA) + << " parent: " << widgetInformation(parent); + parent = parent->parentWidget(); + } + } + break; + + default: + break; + + } + + // always return false to go on with normal chain + return false; +} + +//________________________________________________ +QString WidgetExplorer::eventType(const QEvent::Type &type) const +{ + switch (type) { + case QEvent::MouseButtonPress: + return QStringLiteral("MouseButtonPress"); + case QEvent::MouseButtonRelease: + return QStringLiteral("MouseButtonRelease"); + case QEvent::MouseMove: + return QStringLiteral("MouseMove"); + default: + return QStringLiteral("Unknown"); + } +} + +//________________________________________________ +QString WidgetExplorer::widgetInformation(const QWidget *widget) const +{ + QRect r(widget->geometry()); + const char *className(widget->metaObject()->className()); + QString out; + QTextStream(&out) << widget << " (" << className << ")" + << " position: " << r.x() << "," << r.y() + << " size: " << r.width() << "," << r.height() + << " sizeHint: " << widget->sizeHint().width() << "," << widget->sizeHint().height() + << " minimumSizeHint: " << widget->minimumSizeHint().width() << "," << widget->minimumSizeHint().height() + << " hover: " << widget->testAttribute(Qt::WA_Hover); + + return out; +} + +} // namespace Adwaita diff --git a/src/lib/adwaitawidgetexplorer.h b/src/lib/adwaitawidgetexplorer.h new file mode 100644 index 0000000..62319f5 --- /dev/null +++ b/src/lib/adwaitawidgetexplorer.h @@ -0,0 +1,76 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_WIDGET_EXPLORER_H +#define ADWAITA_WIDGET_EXPLORER_H + +#include "adwaitaqt_export.h" + +#include +#include +#include +#include +#include + +namespace Adwaita +{ +//* print widget's and parent's information on mouse click +class ADWAITAQT_EXPORT WidgetExplorer : public QObject +{ + Q_OBJECT +public: + //* constructor + explicit WidgetExplorer(QObject *parent); + + //* enable + bool enabled() const; + + //* enable + void setEnabled(bool); + + //* widget rects + void setDrawWidgetRects(bool value) + { + _drawWidgetRects = value; + } + + //* event filter + virtual bool eventFilter(QObject *object, QEvent *event); + +protected: + //* event type + QString eventType(const QEvent::Type &type) const; + + //* print widget information + QString widgetInformation(const QWidget *widget) const; + +private: + //* enable state + bool _enabled; + + //* widget rects + bool _drawWidgetRects; + + //* map event types to string + QMap _eventTypes; +}; + +} // namespace Adwaita + +#endif // ADWAITA_WIDGET_EXPLORER_H diff --git a/src/lib/adwaitawindowmanager.cpp b/src/lib/adwaitawindowmanager.cpp new file mode 100644 index 0000000..9244c53 --- /dev/null +++ b/src/lib/adwaitawindowmanager.cpp @@ -0,0 +1,772 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * 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 . * + *************************************************************************/ + +////////////////////////////////////////////////////////////////////////////// +// adwaitawindowmanager.cpp +// pass some window mouse press/release/move event actions to window manager +// ------------------- +// +// Copyright (c) 2014 Hugo Pereira Da Costa +// +// Largely inspired from BeSpin style +// Copyright (C) 2007 Thomas Luebking +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +////////////////////////////////////////////////////////////////////////////// + +#include "adwaitawindowmanager.h" +// #include "adwaitahelper.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// needed to deal with device pixel ratio +#include + +namespace Adwaita +{ + +//* provide application-wise event filter +/** +it us used to unlock dragging and make sure event look is properly restored +after a drag has occurred +*/ +class AppEventFilter: public QObject +{ + +public: + //* constructor + explicit AppEventFilter(WindowManager *parent) + : QObject(parent) + , _parent(parent) + {} + + //* event filter + virtual bool eventFilter(QObject *object, QEvent *event) + { + if (event->type() == QEvent::MouseButtonRelease) { + + // stop drag timer + if (_parent->_dragTimer.isActive()) { + _parent->resetDrag(); + } + + // unlock + if (_parent->isLocked()) { + _parent->setLocked(false); + } + + } + + if (!_parent->enabled()) { + return false; + } + + /* + if a drag is in progress, the widget will not receive any event + we trigger on the first MouseMove or MousePress events that are received + by any widget in the application to detect that the drag is finished + */ + if (_parent->useWMMoveResize() && _parent->_dragInProgress && _parent->_target && (event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonPress)) { + return appMouseEvent(object, event); + } + + return false; + } + +protected: + //* application-wise event. + /** needed to catch end of XMoveResize events */ + bool appMouseEvent(QObject *, QEvent *event) + { + Q_UNUSED(event); + + /* + post some mouseRelease event to the target, in order to counter balance + the mouse press that triggered the drag. Note that it triggers a resetDrag + */ + QMouseEvent mouseEvent(QEvent::MouseButtonRelease, _parent->_dragPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + qApp->sendEvent(_parent->_target.data(), &mouseEvent); + + return false; + } + +private: + //* parent + WindowManager *_parent; +}; + +//_____________________________________________________________ +WindowManager::WindowManager(QObject *parent) + : QObject(parent) + , _enabled(true) + , _useWMMoveResize(true) + , _dragMode(Adwaita::WD_FULL) + , _dragDistance(QApplication::startDragDistance()) + , _dragDelay(QApplication::startDragTime()) + , _dragAboutToStart(false) + , _dragInProgress(false) + , _locked(false) + , _cursorOverride(false) +{ + // install application wise event filter + _appEventFilter = new AppEventFilter(this); + qApp->installEventFilter(_appEventFilter); +} + +//_____________________________________________________________ +void WindowManager::initialize(void) +{ + setEnabled(Adwaita::Config::WindowDragMode != Adwaita::WD_NONE); + setDragMode(Adwaita::Config::WindowDragMode); + setUseWMMoveResize(Adwaita::Config::UseWMMoveResize); + + setDragDistance(QApplication::startDragDistance()); + setDragDelay(QApplication::startDragTime()); + + initializeWhiteList(); + initializeBlackList(); +} + +//_____________________________________________________________ +void WindowManager::registerWidget(QWidget *widget) +{ + if (isBlackListed(widget) || isDragable(widget)) { + /* + install filter for dragable widgets. + also install filter for blacklisted widgets + to be able to catch the relevant events and prevent + the drag to happen + */ + widget->removeEventFilter(this); + widget->installEventFilter(this); + } +} + +//_____________________________________________________________ +void WindowManager::unregisterWidget(QWidget *widget) +{ + if (widget) { + widget->removeEventFilter(this); + } +} + +//_____________________________________________________________ +void WindowManager::initializeWhiteList(void) +{ + _whiteList.clear(); + + // add user specified whitelisted classnames + _whiteList.insert(ExceptionId(QStringLiteral("MplayerWindow"))); + _whiteList.insert(ExceptionId(QStringLiteral("ViewSliders@kmix"))); + _whiteList.insert(ExceptionId(QStringLiteral("Sidebar_Widget@konqueror"))); + + foreach (const QString &exception, Adwaita::Config::WindowDragWhiteList) { + ExceptionId id(exception); + if (!id.className().isEmpty()) { + _whiteList.insert(ExceptionId(exception)); + } + } +} + +//_____________________________________________________________ +void WindowManager::initializeBlackList(void) +{ + _blackList.clear(); + _blackList.insert(ExceptionId(QStringLiteral("CustomTrackView@kdenlive"))); + _blackList.insert(ExceptionId(QStringLiteral("MuseScore"))); + _blackList.insert(ExceptionId(QStringLiteral("KGameCanvasWidget"))); + foreach (const QString &exception, Adwaita::Config::WindowDragBlackList) { + ExceptionId id(exception); + if (!id.className().isEmpty()) { + _blackList.insert(ExceptionId(exception)); + } + } +} + +//_____________________________________________________________ +bool WindowManager::eventFilter(QObject *object, QEvent *event) +{ + if (!enabled()) { + return false; + } + + switch (event->type()) { + case QEvent::MouseButtonPress: + return mousePressEvent(object, event); + break; + case QEvent::MouseMove: + if (object == _target.data()) { + return mouseMoveEvent(object, event); + } + break; + case QEvent::MouseButtonRelease: + if (_target) { + return mouseReleaseEvent(object, event); + } + break; + default: + break; + } + + return false; +} + +//_____________________________________________________________ +void WindowManager::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == _dragTimer.timerId()) { + _dragTimer.stop(); + if (_target) { + startDrag(_target.data(), _globalDragPoint); + } + } else { + return QObject::timerEvent(event); + } +} + +//_____________________________________________________________ +bool WindowManager::mousePressEvent(QObject *object, QEvent *event) +{ + // cast event and check buttons/modifiers + QMouseEvent *mouseEvent = static_cast(event); + if (!(mouseEvent->modifiers() == Qt::NoModifier && mouseEvent->button() == Qt::LeftButton)) { + return false; + } + + // check lock + if (isLocked()) { + return false; + } else { + setLocked(true); + } + + // cast to widget + QWidget *widget = static_cast(object); + + // check if widget can be dragged from current position + if (isBlackListed(widget) || !canDrag(widget)) { + return false; + } + + // retrieve widget's child at event position + QPoint position(mouseEvent->pos()); + QWidget *child = widget->childAt(position); + if (!canDrag(widget, child, position)) { + return false; + } + + // save target and drag point + _target = widget; + _dragPoint = position; + _globalDragPoint = mouseEvent->globalPos(); + _dragAboutToStart = true; + + // send a move event to the current child with same position + // if received, it is caught to actually start the drag + QPoint localPoint(_dragPoint); + if (child) { + localPoint = child->mapFrom(widget, localPoint); + } else { + child = widget; + } + + QMouseEvent localMouseEvent(QEvent::MouseMove, localPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + qApp->sendEvent(child, &localMouseEvent); + + // never eat event + return false; +} + +//_____________________________________________________________ +bool WindowManager::mouseMoveEvent(QObject *object, QEvent *event) +{ + Q_UNUSED(object); + + // stop timer + if (_dragTimer.isActive()) { + _dragTimer.stop(); + } + + // cast event and check drag distance + QMouseEvent *mouseEvent = static_cast(event); + if (!_dragInProgress) { + if (_dragAboutToStart) { + if (mouseEvent->pos() == _dragPoint) { + // start timer, + _dragAboutToStart = false; + if (_dragTimer.isActive()) { + _dragTimer.stop(); + } + _dragTimer.start(_dragDelay, this); + } else { + resetDrag(); + } + } else { + if (QPoint(mouseEvent->globalPos() - _globalDragPoint).manhattanLength() >= _dragDistance) { + _dragTimer.start(0, this); + } + } + + return true; + } else if (!useWMMoveResize()) { + // use QWidget::move for the grabbing + /* this works only if the sending object and the target are identical */ + QWidget *window(_target.data()->window()); + window->move(window->pos() + mouseEvent->pos() - _dragPoint); + return true; + } else { + return false; + } +} + +//_____________________________________________________________ +bool WindowManager::mouseReleaseEvent(QObject *object, QEvent *event) +{ + Q_UNUSED(object); + Q_UNUSED(event); + resetDrag(); + return false; +} + +//_____________________________________________________________ +bool WindowManager::isDragable(QWidget *widget) +{ + // check widget + if (!widget) { + return false; + } + + // accepted default types + if ((qobject_cast(widget) && widget->isWindow()) || + (qobject_cast(widget) && widget->isWindow()) || + qobject_cast(widget)) { + return true; + } + + // more accepted types, provided they are not dock widget titles + if ((qobject_cast(widget) || + qobject_cast(widget) || + qobject_cast(widget) || + qobject_cast(widget)) && + !isDockWidgetTitle(widget)) { + return true; + } + + if (widget->inherits("KScreenSaver") && widget->inherits("KCModule")) { + return true; + } + + if (isWhiteListed(widget)) { + return true; + } + + // flat toolbuttons + if (QToolButton *toolButton = qobject_cast(widget)) { + if (toolButton->autoRaise()) return true; + } + + // viewports + /* + one needs to check that + 1/ the widget parent is a scrollarea + 2/ it matches its parent viewport + 3/ the parent is not blacklisted + */ + if (QListView *listView = qobject_cast(widget->parentWidget())) { + if (listView->viewport() == widget && !isBlackListed(listView)) { + return true; + } + } + + if (QTreeView *treeView = qobject_cast(widget->parentWidget())) { + if (treeView->viewport() == widget && !isBlackListed(treeView)) { + return true; + } + } + + /* + catch labels in status bars. + this is because of kstatusbar + who captures buttonPress/release events + */ + if (QLabel *label = qobject_cast(widget)) { + if (label->textInteractionFlags().testFlag(Qt::TextSelectableByMouse)) { + return false; + } + + QWidget *parent = label->parentWidget(); + while (parent) { + if (qobject_cast(parent)) { + return true; + } + parent = parent->parentWidget(); + } + } + + return false; +} + +//_____________________________________________________________ +bool WindowManager::isBlackListed(QWidget *widget) +{ + // check against noAnimations propery + QVariant propertyValue(widget->property(PropertyNames::noWindowGrab)); + if (propertyValue.isValid() && propertyValue.toBool()) { + return true; + } + + // list-based blacklisted widgets + QString appName(qApp->applicationName()); + foreach (const ExceptionId &id, _blackList) { + if (!id.appName().isEmpty() && id.appName() != appName) { + continue; + } + + if (id.className() == QStringLiteral("*") && !id.appName().isEmpty()) { + // if application name matches and all classes are selected + // disable the grabbing entirely + setEnabled(false); + return true; + } + + if (widget->inherits(id.className().toLatin1().data())) { + return true; + } + } + + return false; +} + +//_____________________________________________________________ +bool WindowManager::isWhiteListed(QWidget *widget) const +{ + QString appName(qApp->applicationName()); + foreach (const ExceptionId &id, _whiteList) { + if (!id.appName().isEmpty() && id.appName() != appName) { + continue; + } + if (widget->inherits(id.className().toLatin1().data())) { + return true; + } + } + + return false; +} + +//_____________________________________________________________ +bool WindowManager::canDrag(QWidget *widget) +{ + // check if enabled + if (!enabled()) { + return false; + } + + // assume isDragable widget is already passed + // check some special cases where drag should not be effective + + // check mouse grabber + if (QWidget::mouseGrabber()) { + return false; + } + + /* + check cursor shape. + Assume that a changed cursor means that some action is in progress + and should prevent the drag + */ + if (widget->cursor().shape() != Qt::ArrowCursor) { + return false; + } + + // accept + return true; +} + +//_____________________________________________________________ +bool WindowManager::canDrag(QWidget *widget, QWidget *child, const QPoint &position) +{ + // retrieve child at given position and check cursor again + if (child && child->cursor().shape() != Qt::ArrowCursor) { + return false; + } + + /* + check against children from which drag should never be enabled, + even if mousePress/Move has been passed to the parent + */ + if (child && (qobject_cast(child) || + qobject_cast(child) || + qobject_cast(child))) { + return false; + } + + // tool buttons + if (QToolButton *toolButton = qobject_cast(widget)) { + if (dragMode() == Adwaita::WD_MINIMAL && !qobject_cast(widget->parentWidget())) { + return false; + } + return toolButton->autoRaise() && !toolButton->isEnabled(); + } + + // check menubar + if (QMenuBar *menuBar = qobject_cast(widget)) { + // do not drag from menubars embedded in Mdi windows + if (findParent(widget)) { + return false; + } + // check if there is an active action + if (menuBar->activeAction() && menuBar->activeAction()->isEnabled()) { + return false; + } + + // check if action at position exists and is enabled + if (QAction *action = menuBar->actionAt(position)) { + if (action->isSeparator()) { + return true; + } + if (action->isEnabled()) { + return false; + } + } + + // return true in all other cases + return true; + + } + + /* + in MINIMAL mode, anything that has not been already accepted + and does not come from a toolbar is rejected + */ + if (dragMode() == Adwaita::WD_MINIMAL) { + if (qobject_cast(widget)) { + return true; + } else { + return false; + } + } + + /* following checks are relevant only for WD_FULL mode */ + + // tabbar. Make sure no tab is under the cursor + if (QTabBar *tabBar = qobject_cast(widget)) { + return tabBar->tabAt(position) == -1; + } + + /* + check groupboxes + prevent drag if unchecking grouboxes + */ + if (QGroupBox *groupBox = qobject_cast(widget)) { + // non checkable group boxes are always ok + if (!groupBox->isCheckable()) { + return true; + } + + // gather options to retrieve checkbox subcontrol rect + QStyleOptionGroupBox opt; + opt.initFrom(groupBox); + + if (groupBox->isFlat()) { + opt.features |= QStyleOptionFrameV2::Flat; + } + opt.lineWidth = 1; + opt.midLineWidth = 0; + opt.text = groupBox->title(); + opt.textAlignment = groupBox->alignment(); + opt.subControls = (QStyle::SC_GroupBoxFrame | QStyle::SC_GroupBoxCheckBox); + + if (!groupBox->title().isEmpty()) { + opt.subControls |= QStyle::SC_GroupBoxLabel; + } + + opt.state |= (groupBox->isChecked() ? QStyle::State_On : QStyle::State_Off); + + // check against groupbox checkbox + if (groupBox->style()->subControlRect(QStyle::CC_GroupBox, &opt, QStyle::SC_GroupBoxCheckBox, groupBox).contains(position)) { + return false; + } + + // check against groupbox label + if (!groupBox->title().isEmpty() && groupBox->style()->subControlRect(QStyle::CC_GroupBox, &opt, QStyle::SC_GroupBoxLabel, groupBox).contains(position)) { + return false; + } + + return true; + } + + // labels + if (QLabel *label = qobject_cast(widget)) { + if (label->textInteractionFlags().testFlag(Qt::TextSelectableByMouse)) return false; + } + + // abstract item views + QAbstractItemView *itemView(nullptr); + if ((itemView = qobject_cast(widget->parentWidget())) || + (itemView = qobject_cast(widget->parentWidget()))) { + if (widget == itemView->viewport()) { + // QListView + if (itemView->frameShape() != QFrame::NoFrame) { + return false; + } else if (itemView->selectionMode() != QAbstractItemView::NoSelection && + itemView->selectionMode() != QAbstractItemView::SingleSelection && + itemView->model() && itemView->model()->rowCount()) { + return false; + } else if (itemView->model() && itemView->indexAt(position).isValid()) { + return false; + } + } + + } else if ((itemView = qobject_cast(widget->parentWidget()))) { + if (widget == itemView->viewport()) { + // QAbstractItemView + if (itemView->frameShape() != QFrame::NoFrame) { + return false; + } else if (itemView->indexAt(position).isValid()) { + return false; + } + } + } else if (QGraphicsView *graphicsView = qobject_cast(widget->parentWidget())) { + if (widget == graphicsView->viewport()) { + // QGraphicsView + if (graphicsView->frameShape() != QFrame::NoFrame) { + return false; + } else if (graphicsView->dragMode() != QGraphicsView::NoDrag) { + return false; + } else if (graphicsView->itemAt(position)) { + return false; + } + } + } + + return true; +} + +//____________________________________________________________ +void WindowManager::resetDrag(void) +{ + if ((!useWMMoveResize()) && _target && _cursorOverride) { + qApp->restoreOverrideCursor(); + _cursorOverride = false; + } + + _target.clear(); + if (_dragTimer.isActive()) { + _dragTimer.stop(); + } + + _dragPoint = QPoint(); + _globalDragPoint = QPoint(); + _dragAboutToStart = false; + _dragInProgress = false; +} + +//____________________________________________________________ +void WindowManager::startDrag(QWidget *widget, const QPoint &position) +{ + if (!(enabled() && widget)) { + return; + } + if (QWidget::mouseGrabber()) { + return; + } + + // ungrab pointer + // TODO uncomment once we move Helper to the library +// if (useWMMoveResize()) { +// if (Helper::isX11()) { +// startDragX11(widget, position); +// } +// } else if (!_cursorOverride) { +// qApp->setOverrideCursor(Qt::SizeAllCursor); +// _cursorOverride = true; +// } + + _dragInProgress = true; + + return; +} + +//_______________________________________________________ +void WindowManager::startDragX11(QWidget *widget, const QPoint &position) +{ + Q_UNUSED(widget); + Q_UNUSED(position); +} + +//____________________________________________________________ +bool WindowManager::supportWMMoveResize(void) const +{ + return false; +} + +//____________________________________________________________ +bool WindowManager::isDockWidgetTitle(const QWidget *widget) const +{ + if (!widget) { + return false; + } + + if (const QDockWidget *dockWidget = qobject_cast(widget->parent())) { + return widget == dockWidget->titleBarWidget(); + } else { + return false; + } +} + +} // namespace Adwaita diff --git a/src/lib/adwaitawindowmanager.h b/src/lib/adwaitawindowmanager.h new file mode 100644 index 0000000..2abc400 --- /dev/null +++ b/src/lib/adwaitawindowmanager.h @@ -0,0 +1,307 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_WINDOW_MANAGER_H +#define ADWAITA_WINDOW_MANAGER_H + +#include "adwaita.h" +#include "adwaitaqt_export.h" +#include "config-adwaita.h" + +#include + +#include +#include +#include +#include +#include + +namespace Adwaita +{ +class ADWAITAQT_EXPORT WindowManager : public QObject +{ + Q_OBJECT +public: + //* constructor + explicit WindowManager(QObject *); + + //* destructor + virtual ~WindowManager() + { + } + + //* initialize + /** read relevant options from config */ + void initialize(); + + //* register widget + void registerWidget(QWidget *); + + //* unregister widget + void unregisterWidget(QWidget *); + + //* event filter [reimplemented] + virtual bool eventFilter(QObject *, QEvent *); + +protected: + //* timer event, + /** used to start drag if button is pressed for a long enough time */ + void timerEvent(QTimerEvent *); + + //* mouse press event + bool mousePressEvent(QObject *, QEvent *); + + //* mouse move event + bool mouseMoveEvent(QObject *, QEvent *); + + //* mouse release event + bool mouseReleaseEvent(QObject *, QEvent *); + + //*@name configuration + //@{ + + //* enable state + bool enabled() const + { + return _enabled; + } + + //* enable state + void setEnabled(bool value) + { + _enabled = value; + } + + //* returns true if window manager is used for moving + bool useWMMoveResize() const + { + return supportWMMoveResize() && _useWMMoveResize; + } + + //* use window manager for moving, when available + void setUseWMMoveResize(bool value) + { + _useWMMoveResize = value; + } + + //* drag mode + int dragMode() const + { + return _dragMode; + } + + //* drag mode + void setDragMode(int value) + { + _dragMode = value; + } + + //* drag distance (pixels) + void setDragDistance(int value) + { + _dragDistance = value; + } + + //* drag delay (msec) + void setDragDelay(int value) + { + _dragDelay = value; + } + + //* set list of whiteListed widgets + /** + white list is read from options and is used to adjust + per-app window dragging issues + */ + void initializeWhiteList(); + + //* set list of blackListed widgets + /** + black list is read from options and is used to adjust + per-app window dragging issues + */ + void initializeBlackList(); + + //@} + + //* returns true if widget is dragable + bool isDragable(QWidget *); + + //* returns true if widget is dragable + bool isBlackListed(QWidget *); + + //* returns true if widget is dragable + bool isWhiteListed(QWidget *) const; + + //* returns true if drag can be started from current widget + bool canDrag(QWidget *); + + //* returns true if drag can be started from current widget and position + /** child at given position is passed as second argument */ + bool canDrag(QWidget *, QWidget *, const QPoint &); + + //* reset drag + void resetDrag(); + + //* start drag + void startDrag(QWidget *, const QPoint &); + + //* X11 specific implementation for startDrag + void startDragX11(QWidget *, const QPoint &); + + //* returns true if window manager is used for moving + /** right now this is true only for X11 */ + bool supportWMMoveResize() const; + + //* utility function + bool isDockWidgetTitle(const QWidget *) const; + + //*@name lock + //@{ + + void setLocked(bool value) + { + _locked = value; + } + + //* lock + bool isLocked() const + { + return _locked; + } + + //@} + + //* returns first widget matching given class, or 0L if none + template T findParent(const QWidget *) const; + +private: + //* enability + bool _enabled; + + //* use WM moveResize + bool _useWMMoveResize; + + //* drag mode + int _dragMode; + + //* drag distance + /** this is copied from kwin::geometry */ + int _dragDistance; + + //* drag delay + /** this is copied from kwin::geometry */ + int _dragDelay; + + //* wrapper for exception id + class ExceptionId : public QPair + { + public: + //* constructor + explicit ExceptionId(const QString &value) + { + const QStringList args(value.split(QChar::fromLatin1('@'))); + if (args.isEmpty()) { + return; + } + + second = args[0].trimmed(); + if (args.size() > 1) { + first = args[1].trimmed(); + } + } + + const QString &appName() const + { + return first; + } + + const QString &className() const + { + return second; + } + }; + + //* exception set + using ExceptionSet = QSet; + + //* list of white listed special widgets + /** + it is read from options and is used to adjust + per-app window dragging issues + */ + ExceptionSet _whiteList; + + //* list of black listed special widgets + /** + it is read from options and is used to adjust + per-app window dragging issues + */ + ExceptionSet _blackList; + + //* drag point + QPoint _dragPoint; + QPoint _globalDragPoint; + + //* drag timer + QBasicTimer _dragTimer; + + //* target being dragged + /** Weak pointer is used in case the target gets deleted while drag is in progress */ + WeakPointer _target; + + //* true if drag is about to start + bool _dragAboutToStart; + + //* true if drag is in progress + bool _dragInProgress; + + //* true if drag is locked + bool _locked; + + //* cursor override + /** used to keep track of application cursor being overridden when dragging in non-WM mode */ + bool _cursorOverride; + + //* application event filter + QObject *_appEventFilter; + + //* allow access of all private members to the app event filter + friend class AppEventFilter; +}; + +//____________________________________________________________________ +template T WindowManager::findParent(const QWidget *widget) const +{ + if (!widget) { + return 0L; + } + + for (QWidget *parent = widget->parentWidget(); parent; parent = parent->parentWidget()) { + if (T cast = qobject_cast(parent)) { + return cast; + } + } + + return 0L; +} + +} // namespace Adwaita + +#endif // ADWAITA_WINDOW_MANAGER_H diff --git a/src/lib/animations/adwaitaanimation.cpp b/src/lib/animations/adwaitaanimation.cpp new file mode 100644 index 0000000..fe46556 --- /dev/null +++ b/src/lib/animations/adwaitaanimation.cpp @@ -0,0 +1,20 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitaanimation.h" diff --git a/src/lib/animations/adwaitaanimation.h b/src/lib/animations/adwaitaanimation.h new file mode 100644 index 0000000..ecbd277 --- /dev/null +++ b/src/lib/animations/adwaitaanimation.h @@ -0,0 +1,67 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_ANIMATION_H +#define ADWAITA_ANIMATION_H + +#include "adwaita.h" +#include "adwaitaqt_export.h" + +#include +#include + +namespace Adwaita +{ +class ADWAITAQT_EXPORT Animation : public QPropertyAnimation +{ + Q_OBJECT +public: + //* convenience + using Pointer = WeakPointer; + + //* constructor + Animation(int duration, QObject *parent) + : QPropertyAnimation(parent) + { + setDuration(duration); + } + + //* destructor + virtual ~Animation() = default; + + //* true if running + bool isRunning() const + { + return state() == Animation::Running; + } + + //* restart + void restart() + { + if (isRunning()) { + stop(); + } + + start(); + } +}; + +} // namespace Adwaita + +#endif // ADWAITA_ANIMATION_H diff --git a/src/lib/animations/adwaitaanimationdata.cpp b/src/lib/animations/adwaitaanimationdata.cpp new file mode 100644 index 0000000..36d36ed --- /dev/null +++ b/src/lib/animations/adwaitaanimationdata.cpp @@ -0,0 +1,38 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitaanimationdata.h" + +namespace Adwaita +{ + +const qreal AnimationData::OpacityInvalid = -1; +int AnimationData::_steps = 0; + +//_________________________________________________________________________________ +void AnimationData::setupAnimation(const Animation::Pointer &animation, const QByteArray &property) +{ + // setup animation + animation.data()->setStartValue(0.0); + animation.data()->setEndValue(1.0); + animation.data()->setTargetObject(this); + animation.data()->setPropertyName(property); +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitaanimationdata.h b/src/lib/animations/adwaitaanimationdata.h new file mode 100644 index 0000000..c61efba --- /dev/null +++ b/src/lib/animations/adwaitaanimationdata.h @@ -0,0 +1,118 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_ANIMATION_DATA_H +#define ADWAITA_ANIMATION_DATA_H + +#include "adwaitaanimation.h" +#include "adwaitaqt_export.h" + +#include +#include +#include + +#include + +namespace Adwaita +{ +//* base class +class ADWAITAQT_EXPORT AnimationData : public QObject +{ + Q_OBJECT +public: + //* constructor + AnimationData(QObject *parent, QWidget *target) + : QObject(parent) + , _target(target) + , _enabled(true) + { + Q_ASSERT(_target); + } + + //* destructor + virtual ~AnimationData() + { + } + + //* duration + virtual void setDuration(int) = 0; + + //* steps + static void setSteps(int value) + { + _steps = value; + } + + //* enability + virtual bool enabled() const + { + return _enabled; + } + + //* enability + virtual void setEnabled(bool value) + { + _enabled = value; + } + + //* target + const WeakPointer &target() const + { + return _target; + } + + //* invalid opacity + static const qreal OpacityInvalid; + +protected: + //* setup animation + virtual void setupAnimation(const Animation::Pointer &animation, const QByteArray &property); + + //* apply step + virtual qreal digitize(const qreal &value) const + { + if (_steps > 0) { + return std::floor(value * _steps) / _steps; + } else { + return value; + } + } + + //* trigger target update + virtual void setDirty() const + { + if (_target) { + _target.data()->update(); + } + } + +private: + //* guarded target + WeakPointer _target; + + //* enability + bool _enabled; + + //* steps + static int _steps; +}; + +} // namespace Adwaita + +#endif // ADWAITA_ANIMATION_DATA_H diff --git a/src/lib/animations/adwaitaanimations.cpp b/src/lib/animations/adwaitaanimations.cpp new file mode 100644 index 0000000..992f479 --- /dev/null +++ b/src/lib/animations/adwaitaanimations.cpp @@ -0,0 +1,221 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitaanimations.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Adwaita +{ + +//____________________________________________________________ +Animations::Animations(QObject *parent) + : QObject(parent) +{ + _widgetEnabilityEngine = new WidgetStateEngine(this); + _busyIndicatorEngine = new BusyIndicatorEngine(this); + _comboBoxEngine = new WidgetStateEngine(this); + _toolButtonEngine = new WidgetStateEngine(this); + _spinBoxEngine = new SpinBoxEngine(this); + _toolBoxEngine = new ToolBoxEngine(this); + + registerEngine(_headerViewEngine = new HeaderViewEngine(this)); + registerEngine(_widgetStateEngine = new WidgetStateEngine(this)); + registerEngine(_inputWidgetEngine = new WidgetStateEngine(this)); + registerEngine(_scrollBarEngine = new ScrollBarEngine(this)); + registerEngine(_stackedWidgetEngine = new StackedWidgetEngine(this)); + registerEngine(_tabBarEngine = new TabBarEngine(this)); + registerEngine(_dialEngine = new DialEngine(this)); +} + +//____________________________________________________________ +void Animations::setupEngines() +{ + // animation steps + AnimationData::setSteps(Adwaita::Config::AnimationSteps); + + bool animationsEnabled(Adwaita::Config::AnimationsEnabled); + int animationsDuration(Adwaita::Config::AnimationsDuration); + + _widgetEnabilityEngine->setEnabled(animationsEnabled); + _comboBoxEngine->setEnabled(animationsEnabled); + _toolButtonEngine->setEnabled(animationsEnabled); + _spinBoxEngine->setEnabled(animationsEnabled); + _toolBoxEngine->setEnabled(animationsEnabled); + + _widgetEnabilityEngine->setDuration(animationsDuration); + _comboBoxEngine->setDuration(animationsDuration); + _toolButtonEngine->setDuration(animationsDuration); + _spinBoxEngine->setDuration(animationsDuration); + _stackedWidgetEngine->setDuration(animationsDuration); + _toolBoxEngine->setDuration(animationsDuration); + + // registered engines + foreach (const BaseEngine::Pointer &engine, _engines) { + engine.data()->setEnabled(animationsEnabled); + engine.data()->setDuration(animationsDuration); + } + + // stacked widget transition has an extra flag for animations + _stackedWidgetEngine->setEnabled(animationsEnabled && Adwaita::Config::StackedWidgetTransitionsEnabled); + + // busy indicator + _busyIndicatorEngine->setEnabled(Adwaita::Config::ProgressBarAnimated); + _busyIndicatorEngine->setDuration(Adwaita::Config::ProgressBarBusyStepDuration); +} + +//____________________________________________________________ +void Animations::registerWidget(QWidget *widget) const +{ + if (!widget) { + return; + } + + // check against noAnimations propery + QVariant propertyValue(widget->property(PropertyNames::noAnimations)); + if (propertyValue.isValid() && propertyValue.toBool()) { + return; + } + + // all widgets are registered to the enability engine. + _widgetEnabilityEngine->registerWidget(widget, AnimationEnable); + + // install animation timers + // for optimization, one should put with most used widgets here first + + // buttons + if (qobject_cast(widget)) { + _toolButtonEngine->registerWidget(widget, AnimationHover | AnimationPressed); + _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationPressed); + } else if (qobject_cast(widget) || qobject_cast(widget)) { + _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus | AnimationPressed); + } else if (qobject_cast(widget)) { + // register to toolbox engine if needed + if (qobject_cast(widget->parent())) { + _toolBoxEngine->registerWidget(widget); + } + + _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationPressed); + // groupboxes + } else if (QGroupBox *groupBox = qobject_cast(widget)) { + if (groupBox->isCheckable()) { + _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus); + } + // sliders + } else if (qobject_cast(widget)) { + _scrollBarEngine->registerWidget(widget, AnimationHover | AnimationFocus); + } else if (qobject_cast(widget)) { + _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus); + } else if (qobject_cast(widget)) { + _dialEngine->registerWidget(widget, AnimationHover | AnimationFocus); + // progress bar + } else if (qobject_cast(widget)) { + _busyIndicatorEngine->registerWidget(widget); + // combo box + } else if (qobject_cast(widget)) { + _comboBoxEngine->registerWidget(widget, AnimationHover | AnimationPressed); + _inputWidgetEngine->registerWidget(widget, AnimationHover | AnimationFocus | AnimationPressed); + // spinbox + } else if (qobject_cast(widget)) { + _spinBoxEngine->registerWidget(widget); + _inputWidgetEngine->registerWidget(widget, AnimationHover | AnimationFocus | AnimationPressed); + // editors + } else if (qobject_cast(widget)) { + _inputWidgetEngine->registerWidget(widget, AnimationHover | AnimationFocus); + } else if (qobject_cast(widget)) { + _inputWidgetEngine->registerWidget(widget, AnimationHover | AnimationFocus); + } else if (widget->inherits("KTextEditor::View")) { + _inputWidgetEngine->registerWidget(widget, AnimationHover | AnimationFocus); + // header views + // need to come before abstract item view, otherwise is skipped + } else if (qobject_cast(widget)) { + _headerViewEngine->registerWidget(widget); + // lists + } else if (qobject_cast(widget)) { + _inputWidgetEngine->registerWidget(widget, AnimationHover | AnimationFocus); + // tabbar + } else if (qobject_cast(widget)) { + _tabBarEngine->registerWidget(widget); + // scrollarea + } else if (QAbstractScrollArea *scrollArea = qobject_cast(widget)) { + if (scrollArea->frameShadow() == QFrame::Sunken && (widget->focusPolicy()&Qt::StrongFocus)) { + _inputWidgetEngine->registerWidget(widget, AnimationHover | AnimationFocus); + } + } + + // stacked widgets + if (QStackedWidget *stack = qobject_cast(widget)) { + _stackedWidgetEngine->registerWidget(stack); + } + + return; +} + +//____________________________________________________________ +void Animations::unregisterWidget(QWidget *widget) const +{ + if (!widget) { + return; + } + + _widgetEnabilityEngine->unregisterWidget(widget); + _spinBoxEngine->unregisterWidget(widget); + _comboBoxEngine->unregisterWidget(widget); + _busyIndicatorEngine->registerWidget(widget); + + // the following allows some optimization of widget unregistration + // it assumes that a widget can be registered atmost in one of the + // engines stored in the list. + foreach (const BaseEngine::Pointer &engine, _engines) { + if (engine && engine.data()->unregisterWidget(widget)) { + break; + } + } +} + +//_______________________________________________________________ +void Animations::unregisterEngine(QObject *object) +{ + int index(_engines.indexOf(qobject_cast(object))); + if (index >= 0) { + _engines.removeAt(index); + } +} + +//_______________________________________________________________ +void Animations::registerEngine(BaseEngine *engine) +{ + _engines.append(engine); + connect(engine, SIGNAL(destroyed(QObject *)), this, SLOT(unregisterEngine(QObject *))); +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitaanimations.h b/src/lib/animations/adwaitaanimations.h new file mode 100644 index 0000000..282464f --- /dev/null +++ b/src/lib/animations/adwaitaanimations.h @@ -0,0 +1,188 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_ANIMATIONS_H +#define ADWAITA_ANIMATIONS_H + +#include "adwaitabusyindicatorengine.h" +#include "adwaitadialengine.h" +#include "adwaitaheaderviewengine.h" +#include "adwaitascrollbarengine.h" +#include "adwaitaspinboxengine.h" +#include "adwaitastackedwidgetengine.h" +#include "adwaitatabbarengine.h" +#include "adwaitatoolboxengine.h" +#include "adwaitawidgetstateengine.h" +#include "adwaitaqt_export.h" + +#include +#include + +namespace Adwaita +{ +//* stores engines +class ADWAITAQT_EXPORT Animations : public QObject +{ + Q_OBJECT + +public: + //* constructor + explicit Animations(QObject *); + + //* destructor + virtual ~Animations() + { + } + + //* register animations corresponding to given widget, depending on its type. + void registerWidget(QWidget *widget) const; + + /** unregister all animations associated to a widget */ + void unregisterWidget(QWidget *widget) const; + + //* enability engine + WidgetStateEngine &widgetEnabilityEngine() const + { + return *_widgetEnabilityEngine; + } + + //* abstractButton engine + WidgetStateEngine &widgetStateEngine() const + { + return *_widgetStateEngine; + } + + //* editable combobox arrow hover engine + WidgetStateEngine &comboBoxEngine() const + { + return *_comboBoxEngine; + } + + //! Tool buttons arrow hover engine + WidgetStateEngine &toolButtonEngine() const + { + return *_toolButtonEngine; + } + + //! item view engine + WidgetStateEngine &inputWidgetEngine() const + { + return *_inputWidgetEngine; + } + + //* busy indicator + BusyIndicatorEngine &busyIndicatorEngine() const + { + return *_busyIndicatorEngine; + } + + //* header view engine + HeaderViewEngine &headerViewEngine() const + { + return *_headerViewEngine; + } + + //* scrollbar engine + ScrollBarEngine &scrollBarEngine() const + { + return *_scrollBarEngine; + } + + //* dial engine + DialEngine &dialEngine() const + { + return *_dialEngine; + } + + //* spinbox engine + SpinBoxEngine &spinBoxEngine() const + { + return *_spinBoxEngine; + } + + //* tabbar + TabBarEngine &tabBarEngine() const + { + return *_tabBarEngine; + } + + //* toolbox + ToolBoxEngine &toolBoxEngine() const + { + return *_toolBoxEngine; + } + + //* setup engines + void setupEngines(); + +protected Q_SLOTS: + + //* enregister engine + void unregisterEngine(QObject *); + +private: + //* register new engine + void registerEngine(BaseEngine *engine); + + //* busy indicator + BusyIndicatorEngine *_busyIndicatorEngine; + + //* headerview hover effect + HeaderViewEngine *_headerViewEngine; + + //* widget enability engine + WidgetStateEngine *_widgetEnabilityEngine; + + //* abstract button engine + WidgetStateEngine *_widgetStateEngine; + + //* editable combobox arrow hover effect + WidgetStateEngine *_comboBoxEngine; + + //! mennu toolbutton arrow hover effect + WidgetStateEngine *_toolButtonEngine; + + //! item view engine + WidgetStateEngine *_inputWidgetEngine; + + //* scrollbar engine + ScrollBarEngine *_scrollBarEngine; + + //* dial engine + DialEngine *_dialEngine; + + //* spinbox engine + SpinBoxEngine *_spinBoxEngine; + + //* stacked widget engine + StackedWidgetEngine *_stackedWidgetEngine; + + //* tabbar engine + TabBarEngine *_tabBarEngine; + + //* toolbar engine + ToolBoxEngine *_toolBoxEngine; + + //* keep list of existing engines + QList _engines; +}; + +} // namespace Adwaita + +#endif // ADWAITA_ANIMATIONS_H diff --git a/src/lib/animations/adwaitabaseengine.cpp b/src/lib/animations/adwaitabaseengine.cpp new file mode 100644 index 0000000..7ded79a --- /dev/null +++ b/src/lib/animations/adwaitabaseengine.cpp @@ -0,0 +1,20 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitabaseengine.h" diff --git a/src/lib/animations/adwaitabaseengine.h b/src/lib/animations/adwaitabaseengine.h new file mode 100644 index 0000000..b9953ef --- /dev/null +++ b/src/lib/animations/adwaitabaseengine.h @@ -0,0 +1,98 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_BASE_ENGINE_H +#define ADWAITA_BASE_ENGINE_H + +#include "adwaita.h" +#include "adwaitaqt_export.h" + +#include +#include + +namespace Adwaita +{ +//* base class for all animation engines +/** it is used to store configuration values used by all animations stored in the engine */ +class ADWAITAQT_EXPORT BaseEngine : public QObject +{ + Q_OBJECT +public: + using Pointer = WeakPointer; + + //* constructor + explicit BaseEngine(QObject *parent) + : QObject(parent) + , _enabled(true) + , _duration(200) + { + } + + //* destructor + virtual ~BaseEngine() + { + } + + //* enability + virtual void setEnabled(bool value) + { + _enabled = value; + } + + //* enability + virtual bool enabled() const + { + return _enabled; + } + + //* duration + virtual void setDuration(int value) + { + _duration = value; + } + + //* duration + virtual int duration() const + { + return _duration; + } + + //* unregister widget + virtual bool unregisterWidget(QObject *object) = 0; + + //* list of widgets + using WidgetList = QSet; + + //* returns registered widgets + virtual WidgetList registeredWidgets() const + { + return WidgetList(); + } + +private: + //* engine enability + bool _enabled; + + //* animation duration + int _duration; +}; + +} // namespace Adwaita + +#endif // ADWAITA_BASE_ENGINE_H diff --git a/src/lib/animations/adwaitabusyindicatordata.cpp b/src/lib/animations/adwaitabusyindicatordata.cpp new file mode 100644 index 0000000..ebe3340 --- /dev/null +++ b/src/lib/animations/adwaitabusyindicatordata.cpp @@ -0,0 +1,20 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitabusyindicatordata.h" diff --git a/src/lib/animations/adwaitabusyindicatordata.h b/src/lib/animations/adwaitabusyindicatordata.h new file mode 100644 index 0000000..b491747 --- /dev/null +++ b/src/lib/animations/adwaitabusyindicatordata.h @@ -0,0 +1,84 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_BUSY_INDICATOR_DATA_ +#define ADWAITA_BUSY_INDICATOR_DATA_ + +#include "adwaitaqt_export.h" + +#include + +namespace Adwaita +{ +class Q_DECL_EXPORT BusyIndicatorData : public QObject +{ + Q_OBJECT +public: + //* constructor + explicit BusyIndicatorData(QObject *parent) + : QObject(parent) + , _animated(false) + { + } + + //* destructor + virtual ~BusyIndicatorData() + { + } + + //*@name accessors + //@{ + + //* animated + bool isAnimated() const + { + return _animated; + } + + //@} + + //*@name modifiers + //@{ + + //* enabled + void setEnabled(bool) + { + } + + //* enabled + void setDuration(int) + { + } + + //* animated + void setAnimated(bool value) + { + _animated = value; + } + + //@} + +private: + //* animated + bool _animated; +}; + +} // namespace Adwaita + +#endif // ADWAITA_BUSY_INDICATOR_DATA_ diff --git a/src/lib/animations/adwaitabusyindicatorengine.cpp b/src/lib/animations/adwaitabusyindicatorengine.cpp new file mode 100644 index 0000000..19c7dae --- /dev/null +++ b/src/lib/animations/adwaitabusyindicatorengine.cpp @@ -0,0 +1,156 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitabusyindicatorengine.h" +#include "adwaita.h" + +#include + +namespace Adwaita +{ + +//_______________________________________________ +BusyIndicatorEngine::BusyIndicatorEngine(QObject *object) + : BaseEngine(object) +{} + +//_______________________________________________ +bool BusyIndicatorEngine::registerWidget(QObject *object) +{ + // check widget validity + if (!object) return false; + + // create new data class + if (!_data.contains(object)) { + _data.insert(object, new BusyIndicatorData(this)); + + // connect destruction signal + connect(object, SIGNAL(destroyed(QObject *)), this, SLOT(unregisterWidget(QObject *)), Qt::UniqueConnection); + } + + return true; +} + +//____________________________________________________________ +bool BusyIndicatorEngine::isAnimated(const QObject *object) +{ + DataMap::Value data(BusyIndicatorEngine::data(object)); + return data && data.data()->isAnimated(); +} + +//____________________________________________________________ +void BusyIndicatorEngine::setDuration(int value) +{ + if (duration() == value) { + return; + } + + BaseEngine::setDuration(value); + + // restart timer with specified time + if (_animation) { + _animation.data()->setDuration(value); + } +} + +//____________________________________________________________ +void BusyIndicatorEngine::setAnimated(const QObject *object, bool value) +{ + DataMap::Value data(BusyIndicatorEngine::data(object)); + if (data) { + // update data + data.data()->setAnimated(value); + + // start timer if needed + if (value) { + if (!_animation) { + // create animation if not already there + _animation = new Animation(duration(), this); + + // setup + _animation.data()->setStartValue(0.0); + _animation.data()->setEndValue(100.0); + _animation.data()->setTargetObject(this); + _animation.data()->setPropertyName("value"); + _animation.data()->setLoopCount(-1); + _animation.data()->setDuration(duration() * 3); + } + + // start if not already running + if (!_animation.data()->isRunning()) { + _animation.data()->start(); + } + } + } + + return; +} + +//____________________________________________________________ +DataMap::Value BusyIndicatorEngine::data(const QObject *object) +{ + return _data.find(object).data(); +} + +//_______________________________________________ +void BusyIndicatorEngine::setValue(int value) +{ + // update + _value = value; + + bool animated(false); + + // loop over objects in map + for (DataMap::iterator iter = _data.begin(); iter != _data.end(); ++iter) { + if (iter.value().data()->isAnimated()) { + // update animation flag + animated = true; + + // emit update signal on object + if (const_cast(iter.key())->inherits("QQuickStyleItem")) { + //QtQuickControls "rerender" method is updateItem + QMetaObject::invokeMethod(const_cast(iter.key()), "updateItem", Qt::QueuedConnection); + } else { + + QMetaObject::invokeMethod(const_cast(iter.key()), "update", Qt::QueuedConnection); + } + } + } + + if (_animation && !animated) { + _animation.data()->stop(); + _animation.data()->deleteLater(); + _animation.clear(); + } +} + +//__________________________________________________________ +bool BusyIndicatorEngine::unregisterWidget(QObject *object) +{ + bool removed(_data.unregisterWidget(object)); + if (_animation && _data.isEmpty()) { + _animation.data()->stop(); + _animation.data()->deleteLater(); + _animation.clear(); + } + + return removed; +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitabusyindicatorengine.h b/src/lib/animations/adwaitabusyindicatorengine.h new file mode 100644 index 0000000..ba5421d --- /dev/null +++ b/src/lib/animations/adwaitabusyindicatorengine.h @@ -0,0 +1,101 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_BUSY_INDICATOR_ENGINE_H +#define ADWAITA_BUSY_INDICATOR_ENGINE_H + +#include "adwaitaanimation.h" +#include "adwaitabaseengine.h" +#include "adwaitabusyindicatordata.h" +#include "adwaitadatamap.h" +#include "adwaitaqt_export.h" + +namespace Adwaita +{ +//* handles progress bar animations +class ADWAITAQT_EXPORT BusyIndicatorEngine : public BaseEngine +{ + Q_OBJECT + + //* declare opacity property + Q_PROPERTY(int value READ value WRITE setValue) + +public: + //* constructor + explicit BusyIndicatorEngine(QObject *); + + //* destructor + virtual ~BusyIndicatorEngine() + { + } + + //*@name accessors + //@{ + + //* true if widget is animated + virtual bool isAnimated(const QObject *); + + //* value + virtual int value() const + { + return _value; + } + + //@} + + //*@name modifiers + //@{ + + //* register progressbar + virtual bool registerWidget(QObject *); + + //* duration + virtual void setDuration(int); + + //* set object as animated + virtual void setAnimated(const QObject *, bool); + + //* opacity + virtual void setValue(int value); + + //@} + +public Q_SLOTS: + + //* remove widget from map + virtual bool unregisterWidget(QObject *); + +protected: + //* returns data associated to widget + DataMap::Value data(const QObject *); + +private: + //* map widgets to progressbar data + DataMap _data; + + //* animation + Animation::Pointer _animation; + + //* value + int _value = 0; +}; + +} // namespace Adwaita + +#endif // ADWAITA_BUSY_INDICATOR_ENGINE_H diff --git a/src/lib/animations/adwaitadatamap.h b/src/lib/animations/adwaitadatamap.h new file mode 100644 index 0000000..9883178 --- /dev/null +++ b/src/lib/animations/adwaitadatamap.h @@ -0,0 +1,183 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_DATAMAP_H +#define ADWAITA_DATAMAP_H + +#include "adwaita.h" +#include "adwaitaqt_export.h" + +#include +#include +#include + +namespace Adwaita +{ +//* data map +/** it maps templatized data object to associated object */ +template class ADWAITAQT_EXPORT BaseDataMap : public QMap> +{ +public: + using Key = const K *; + using Value = WeakPointer; + + //* constructor + BaseDataMap() + : QMap() + , _enabled(true) + , _lastKey(NULL) + { + } + + //* destructor + virtual ~BaseDataMap() + { + } + + //* insertion + virtual typename QMap::iterator insert(const Key &key, const Value &value, bool enabled = true) + { + if (value) { + value.data()->setEnabled(enabled); + } + return QMap::insert(key, value); + } + + //* find value + Value find(Key key) + { + if (!(enabled() && key)) { + return Value(); + } + if (key == _lastKey) { + return _lastValue; + } else { + Value out; + typename QMap::iterator iter(QMap::find(key)); + if (iter != QMap::end()) { + out = iter.value(); + } + _lastKey = key; + _lastValue = out; + return out; + } + } + + //* unregister widget + bool unregisterWidget(Key key) + { + // check key + if (!key) { + return false; + } + + // clear last value if needed + if (key == _lastKey) { + if (_lastValue) { + _lastValue.clear(); + } + _lastKey = NULL; + } + + // find key in map + typename QMap::iterator iter(QMap::find(key)); + if (iter == QMap::end()) { + return false; + } + + // delete value from map if found + if (iter.value()) { + iter.value().data()->deleteLater(); + } + QMap::erase(iter); + + return true; + } + + //* maxFrame + void setEnabled(bool enabled) + { + _enabled = enabled; + foreach (const Value &value, *this) { + if (value) { + value.data()->setEnabled(enabled); + } + } + } + + //* enability + bool enabled() const + { + return _enabled; + } + + //* duration + void setDuration(int duration) const + { + foreach (const Value &value, *this) { + if (value) { + value.data()->setDuration(duration); + } + } + } + +private: + //* enability + bool _enabled; + + //* last key + Key _lastKey; + + //* last value + Value _lastValue; +}; + +//* standard data map, using QObject as a key +template class ADWAITAQT_EXPORT DataMap : public BaseDataMap +{ +public: + //* constructor + DataMap() + { + } + + //* destructor + virtual ~DataMap() + { + } +}; + +//* QPaintDevice based dataMap +template class ADWAITAQT_EXPORT PaintDeviceDataMap : public BaseDataMap +{ +public: + //* constructor + PaintDeviceDataMap() + { + } + + //* destructor + virtual ~PaintDeviceDataMap() + { + } +}; + +} // namespace Adwaiat + +#endif // ADWAITA_DATAMAP_H diff --git a/src/lib/animations/adwaitadialdata.cpp b/src/lib/animations/adwaitadialdata.cpp new file mode 100644 index 0000000..cbcf735 --- /dev/null +++ b/src/lib/animations/adwaitadialdata.cpp @@ -0,0 +1,88 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitadialdata.h" + +#include +#include + +namespace Adwaita +{ + +//______________________________________________ +DialData::DialData(QObject *parent, QWidget *target, int duration) + : WidgetStateData(parent, target, duration) + , _position(-1, -1) +{ + target->installEventFilter(this); +} + +//______________________________________________ +bool DialData::eventFilter(QObject *object, QEvent *event) +{ + if (object != target().data()) { + return WidgetStateData::eventFilter(object, event); + } + + // check event type + switch (event->type()) { + case QEvent::HoverEnter: + case QEvent::HoverMove: + hoverMoveEvent(object, event); + break; + case QEvent::HoverLeave: + hoverLeaveEvent(object, event); + break; + default: + break; + } + + return WidgetStateData::eventFilter(object, event); +} + +//______________________________________________ +void DialData::hoverMoveEvent(QObject *object, QEvent *event) +{ + // try cast object to dial + QDial *scrollBar(qobject_cast(object)); + if (!scrollBar || scrollBar->isSliderDown()) { + return; + } + + // cast event + QHoverEvent *hoverEvent = static_cast(event); + + // store position + _position = hoverEvent->pos(); + + // trigger animation if position match handle rect + updateState(_handleRect.contains(_position)); +} + +//______________________________________________ +void DialData::hoverLeaveEvent(QObject *, QEvent *) +{ + // reset hover state + updateState(false); + + // reset mouse position + _position = QPoint(-1, -1); +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitadialdata.h b/src/lib/animations/adwaitadialdata.h new file mode 100644 index 0000000..63e544a --- /dev/null +++ b/src/lib/animations/adwaitadialdata.h @@ -0,0 +1,73 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_DIAL_DATA_H +#define ADWAITA_DIAL_DATA_H + +#include "adwaitawidgetstatedata.h" +#include "adwaitaqt_export.h" + +namespace Adwaita +{ +//* dial data +class ADWAITAQT_EXPORT DialData : public WidgetStateData +{ + Q_OBJECT +public: + //* constructor + DialData(QObject *parent, QWidget *target, int); + + //* destructor + virtual ~DialData() + { + } + + //* event filter + virtual bool eventFilter(QObject *, QEvent *); + + //* subcontrol rect + virtual void setHandleRect(const QRect &rect) + { + _handleRect = rect; + } + + //* mouse position + QPoint position() const + { + return _position; + } + +protected: + //* hoverMoveEvent + virtual void hoverMoveEvent(QObject *, QEvent *); + + //* hoverMoveEvent + virtual void hoverLeaveEvent(QObject *, QEvent *); + +private: + //* rect + QRect _handleRect; + + //* mouse position + QPoint _position; +}; + +} // namespace Adwaita + +#endif // ADWAITA_DIAL_DATA_H diff --git a/src/lib/animations/adwaitadialengine.cpp b/src/lib/animations/adwaitadialengine.cpp new file mode 100644 index 0000000..234af6d --- /dev/null +++ b/src/lib/animations/adwaitadialengine.cpp @@ -0,0 +1,49 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitadialengine.h" + +#include + +namespace Adwaita +{ + +//____________________________________________________________ +bool DialEngine::registerWidget(QWidget *widget, AnimationModes mode) +{ + // check widget + if (!widget) { + return false; + } + + // only handle hover and focus + if (mode & AnimationHover && !dataMap(AnimationHover).contains(widget)) { + dataMap(AnimationHover).insert(widget, new DialData(this, widget, duration()), enabled()); + } + if (mode & AnimationFocus && !dataMap(AnimationFocus).contains(widget)) { + dataMap(AnimationFocus).insert(widget, new WidgetStateData(this, widget, duration()), enabled()); + } + + // connect destruction signal + connect(widget, SIGNAL(destroyed(QObject *)), this, SLOT(unregisterWidget(QObject *)), Qt::UniqueConnection); + + return true; +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitadialengine.h b/src/lib/animations/adwaitadialengine.h new file mode 100644 index 0000000..de117cc --- /dev/null +++ b/src/lib/animations/adwaitadialengine.h @@ -0,0 +1,69 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_DIAL_ENGINE_H +#define ADWAITA_DIAL_ENGINE_H + +#include "adwaitadialdata.h" +#include "adwaitawidgetstateengine.h" +#include "adwaitaqt_export.h" + +namespace Adwaita +{ +//* stores dial hovered action and timeLine +class ADWAITAQT_EXPORT DialEngine : public WidgetStateEngine +{ + Q_OBJECT +public: + //* constructor + explicit DialEngine(QObject *parent) + : WidgetStateEngine(parent) + { + } + + //* destructor + virtual ~DialEngine() + { + } + + //* register dial + virtual bool registerWidget(QWidget *, AnimationModes); + + //* control rect + virtual void setHandleRect(const QObject *object, const QRect &rect) + { + if (DataMap::Value data = this->data(object, AnimationHover)) { + static_cast(data.data())->setHandleRect(rect); + } + } + + //* mouse position + virtual QPoint position(const QObject *object) + { + if (DataMap::Value data = this->data(object, AnimationHover)) { + return static_cast(data.data())->position(); + } else { + return QPoint(-1, -1); + } + } +}; + +} // namespace Adwaita + +#endif // ADWAITA_DIAL_ENGINE_H diff --git a/src/lib/animations/adwaitaenabledata.cpp b/src/lib/animations/adwaitaenabledata.cpp new file mode 100644 index 0000000..b20f8a8 --- /dev/null +++ b/src/lib/animations/adwaitaenabledata.cpp @@ -0,0 +1,48 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitaenabledata.h" + +namespace Adwaita +{ + +//______________________________________________ +bool EnableData::eventFilter(QObject *object, QEvent *event) +{ + if (!enabled()) { + return WidgetStateData::eventFilter(object, event); + } + + // check event type + switch (event->type()) { + // enter event + case QEvent::EnabledChange: { + if (QWidget *widget = qobject_cast(object)) { + updateState(widget->isEnabled()); + } + break; + } + default: + break; + } + + return WidgetStateData::eventFilter(object, event); +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitaenabledata.h b/src/lib/animations/adwaitaenabledata.h new file mode 100644 index 0000000..fcad8c7 --- /dev/null +++ b/src/lib/animations/adwaitaenabledata.h @@ -0,0 +1,51 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_ENABLE_DATA_H +#define ADWAITA_ENABLE_DATA_H + +#include "adwaitawidgetstatedata.h" +#include "adwaitaqt_export.h" + +namespace Adwaita +{ +//* Enable data +class ADWAITAQT_EXPORT EnableData : public WidgetStateData +{ + Q_OBJECT +public: + //* constructor + EnableData(QObject *parent, QWidget *target, int duration, bool state = true) + : WidgetStateData(parent, target, duration, state) + { + target->installEventFilter(this); + } + + //* destructor + virtual ~EnableData() + { + } + + //* event filter + virtual bool eventFilter(QObject *, QEvent *); +}; + +} // namespace Adwaita + +#endif // ADWAITA_ENABLE_DATA_H diff --git a/src/lib/animations/adwaitagenericdata.cpp b/src/lib/animations/adwaitagenericdata.cpp new file mode 100644 index 0000000..a9181ba --- /dev/null +++ b/src/lib/animations/adwaitagenericdata.cpp @@ -0,0 +1,36 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitagenericdata.h" + +#include + +namespace Adwaita +{ + +//______________________________________________ +GenericData::GenericData(QObject *parent, QWidget *target, int duration) + : AnimationData(parent, target) + , _animation(new Animation(duration, this)) + , _opacity(0) +{ + setupAnimation(_animation, "opacity"); +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitagenericdata.h b/src/lib/animations/adwaitagenericdata.h new file mode 100644 index 0000000..a8184b0 --- /dev/null +++ b/src/lib/animations/adwaitagenericdata.h @@ -0,0 +1,89 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_GENERIC_DATA_H +#define ADWAITA_GENERIC_DATA_H + +#include "adwaitaanimation.h" +#include "adwaitaanimationdata.h" +#include "adwaitaqt_export.h" + +#include +#include + +namespace Adwaita +{ +//* generic data +class ADWAITAQT_EXPORT GenericData : public AnimationData +{ + Q_OBJECT + + //* declare opacity property + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity) + +public: + //* constructor + GenericData(QObject *parent, QWidget *widget, int duration); + + //* destructor + virtual ~GenericData() + { + } + + //* return animation object + virtual const Animation::Pointer &animation() const + { + return _animation; + } + + //* duration + virtual void setDuration(int duration) + { + _animation.data()->setDuration(duration); + } + + //* opacity + virtual qreal opacity() const + { + return _opacity; + } + + //* opacity + virtual void setOpacity(qreal value) + { + value = digitize(value); + if (_opacity == value) { + return; + } + + _opacity = value; + setDirty(); + } + +private: + //* animation handling + Animation::Pointer _animation; + + //* opacity variable + qreal _opacity; +}; + +} // namespace Adwaita + +#endif // ADWAITA_GENERIC_DATA_H diff --git a/src/lib/animations/adwaitaheaderviewdata.cpp b/src/lib/animations/adwaitaheaderviewdata.cpp new file mode 100644 index 0000000..276307c --- /dev/null +++ b/src/lib/animations/adwaitaheaderviewdata.cpp @@ -0,0 +1,189 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * 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 . * + *************************************************************************/ + +////////////////////////////////////////////////////////////////////////////// +// adwaitaheaderviewdata.cpp +// data container for QHeaderView animations +// ------------------- +// +// Copyright (c) 2009 Hugo Pereira Da Costa +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +////////////////////////////////////////////////////////////////////////////// + +#include "adwaitaheaderviewdata.h" + +#include +#include + +namespace Adwaita +{ + +//______________________________________________ +HeaderViewData::HeaderViewData(QObject *parent, QWidget *target, int duration) + : AnimationData(parent, target) +{ + _current._animation = new Animation(duration, this); + setupAnimation(currentIndexAnimation(), "currentOpacity"); + currentIndexAnimation().data()->setDirection(Animation::Forward); + + _previous._animation = new Animation(duration, this); + setupAnimation(previousIndexAnimation(), "previousOpacity"); + previousIndexAnimation().data()->setDirection(Animation::Backward); +} + +//______________________________________________ +bool HeaderViewData::updateState(const QPoint &position, bool hovered) +{ + if (!enabled()) { + return false; + } + + const QHeaderView *local(qobject_cast(target().data())); + if (!local) { + return false; + } + + int index(local->logicalIndexAt(position)); + if (index < 0) { + return false; + } + + if (hovered) { + if (index != currentIndex()) { + if (currentIndex() >= 0) { + setPreviousIndex(currentIndex()); + setCurrentIndex(-1); + previousIndexAnimation().data()->restart(); + } + + setCurrentIndex(index); + currentIndexAnimation().data()->restart(); + + return true; + } else { + return false; + } + } else if (index == currentIndex()) { + setPreviousIndex(currentIndex()); + setCurrentIndex(-1); + previousIndexAnimation().data()->restart(); + return true; + } else { + return false; + } +} + +//______________________________________________ +Animation::Pointer HeaderViewData::animation(const QPoint &position) const +{ + if (!enabled()) { + return Animation::Pointer(); + } + + const QHeaderView *local(qobject_cast(target().data())); + if (!local) { + return Animation::Pointer(); + } + + int index(local->logicalIndexAt(position)); + if (index < 0) { + return Animation::Pointer(); + } else if (index == currentIndex()) { + return currentIndexAnimation(); + } else if (index == previousIndex()) { + return previousIndexAnimation(); + } else { + return Animation::Pointer(); + } +} + +//______________________________________________ +qreal HeaderViewData::opacity(const QPoint &position) const +{ + if (!enabled()) { + return OpacityInvalid; + } + + const QHeaderView *local(qobject_cast(target().data())); + if (!local) { + return OpacityInvalid; + } + + int index(local->logicalIndexAt(position)); + if (index < 0) { + return OpacityInvalid; + } else if (index == currentIndex()) { + return currentOpacity(); + } else if (index == previousIndex()) { + return previousOpacity(); + } else { + return OpacityInvalid; + } +} + +//__________________________________________________________ +void HeaderViewData::setDirty() const +{ + QHeaderView *header = qobject_cast(target().data()); + if (!header) { + return; + } + + // get first and last index, sorted + int lastIndex(qMax(previousIndex(), currentIndex())); + if (lastIndex < 0) { + return; + } + + int firstIndex(qMin(previousIndex(), currentIndex())); + if (firstIndex < 0) { + firstIndex = lastIndex; + } + + // find relevant rectangle to be updated, in viewport coordinate + QWidget *viewport(header->viewport()); + int left = header->sectionViewportPosition(firstIndex); + int right = header->sectionViewportPosition(lastIndex) + header->sectionSize(lastIndex); + + // trigger update + if (header->orientation() == Qt::Horizontal) { + viewport->update(left, 0, right - left, header->height()); + } else { + viewport->update(0, left, header->width(), right - left); + } +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitaheaderviewdata.h b/src/lib/animations/adwaitaheaderviewdata.h new file mode 100644 index 0000000..119f0e4 --- /dev/null +++ b/src/lib/animations/adwaitaheaderviewdata.h @@ -0,0 +1,175 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_HEADER_VIEW_DATA_H +#define ADWAITA_HEADER_VIEW_DATA_H + +#include "adwaitaanimationdata.h" +#include "adwaitaqt_export.h" + +#include + +namespace Adwaita +{ +//* headerviews +class ADWAITAQT_EXPORT HeaderViewData : public AnimationData +{ + Q_OBJECT + + //* declare opacity property + Q_PROPERTY(qreal currentOpacity READ currentOpacity WRITE setCurrentOpacity) + Q_PROPERTY(qreal previousOpacity READ previousOpacity WRITE setPreviousOpacity) +public: + //* constructor + HeaderViewData(QObject *parent, QWidget *target, int duration); + + //* destructor + virtual ~HeaderViewData() + { + } + + //* duration + void setDuration(int duration) + { + currentIndexAnimation().data()->setDuration(duration); + previousIndexAnimation().data()->setDuration(duration); + } + + //* update state + bool updateState(const QPoint &, bool); + + //*@name current index handling + //@{ + + //* current opacity + virtual qreal currentOpacity() const + { + return _current._opacity; + } + + //* current opacity + virtual void setCurrentOpacity(qreal value) + { + value = digitize(value); + if (_current._opacity == value) { + return; + } + + _current._opacity = value; + setDirty(); + } + + //* current index + virtual int currentIndex() const + { + return _current._index; + } + + //* current index + virtual void setCurrentIndex(int index) + { + _current._index = index; + } + + //* current index animation + virtual const Animation::Pointer ¤tIndexAnimation() const + { + return _current._animation; + } + + //@} + + //*@name previous index handling + //@{ + + //* previous opacity + virtual qreal previousOpacity() const + { + return _previous._opacity; + } + + //* previous opacity + virtual void setPreviousOpacity(qreal value) + { + value = digitize(value); + if (_previous._opacity == value) { + return; + } + + _previous._opacity = value; + setDirty(); + } + + //* previous index + virtual int previousIndex() const + { + return _previous._index; + } + + //* previous index + virtual void setPreviousIndex(int index) + { + _previous._index = index; + } + + //* previous index Animation + virtual const Animation::Pointer &previousIndexAnimation() const + { + return _previous._animation; + } + + //@} + + //* return Animation associated to action at given position, if any + virtual Animation::Pointer animation(const QPoint &position) const; + + //* return opacity associated to action at given position, if any + virtual qreal opacity(const QPoint &position) const; + +protected: + //* dirty + virtual void setDirty() const; + +private: + //* container for needed animation data + class Data + { + public: + //* default constructor + Data() + : _opacity(0) + , _index(-1) + { + } + + Animation::Pointer _animation; + qreal _opacity; + int _index; + }; + + //* current tab animation data (for hover enter animations) + Data _current; + + //* previous tab animations data (for hover leave animations) + Data _previous; +}; + +} // namespace Adwaita + +#endif // ADWAITA_HEADER_VIEW_DATA_H diff --git a/src/lib/animations/adwaitaheaderviewengine.cpp b/src/lib/animations/adwaitaheaderviewengine.cpp new file mode 100644 index 0000000..cd606b7 --- /dev/null +++ b/src/lib/animations/adwaitaheaderviewengine.cpp @@ -0,0 +1,51 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitaheaderviewengine.h" + +#include + +namespace Adwaita +{ + +//____________________________________________________________ +bool HeaderViewEngine::registerWidget(QWidget *widget) +{ + if (!widget) { + return false; + } + + // create new data class + if (!_data.contains(widget)) { + _data.insert(widget, new HeaderViewData(this, widget, duration()), enabled()); + } + + // connect destruction signal + connect(widget, SIGNAL(destroyed(QObject *)), this, SLOT(unregisterWidget(QObject *)), Qt::UniqueConnection); + return true; +} + +//____________________________________________________________ +bool HeaderViewEngine::updateState(const QObject *object, const QPoint &position, bool value) +{ + DataMap::Value data(_data.find(object)); + return (data && data.data()->updateState(position, value)); +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitaheaderviewengine.h b/src/lib/animations/adwaitaheaderviewengine.h new file mode 100644 index 0000000..a542b08 --- /dev/null +++ b/src/lib/animations/adwaitaheaderviewengine.h @@ -0,0 +1,99 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_HEADER_VIEW_ENGINE_H +#define ADWAITA_HEADER_VIEW_ENGINE_H + +#include "adwaitabaseengine.h" +#include "adwaitadatamap.h" +#include "adwaitaheaderviewdata.h" +#include "adwaitaqt_export.h" + +namespace Adwaita +{ +//* stores headerview hovered action and timeLine +class ADWAITAQT_EXPORT HeaderViewEngine : public BaseEngine +{ + Q_OBJECT +public: + //* constructor + explicit HeaderViewEngine(QObject *parent) + : BaseEngine(parent) + { + } + + //* destructor + virtual ~HeaderViewEngine() + { + } + + //* register headerview + virtual bool registerWidget(QWidget *); + + //* true if widget hover state is changed + virtual bool updateState(const QObject *, const QPoint &, bool); + + //* true if widget is animated + virtual bool isAnimated(const QObject *object, const QPoint &point) + { + if (DataMap::Value data = _data.find(object)) { + if (Animation::Pointer animation = data.data()->animation(point)) { + return animation.data()->isRunning(); + } + } + + return false; + } + + //* animation opacity + virtual qreal opacity(const QObject *object, const QPoint &point) + { + return isAnimated(object, point) ? _data.find(object).data()->opacity(point) : AnimationData::OpacityInvalid; + } + + //* enability + virtual void setEnabled(bool value) + { + BaseEngine::setEnabled(value); + _data.setEnabled(value); + } + + //* duration + virtual void setDuration(int value) + { + BaseEngine::setDuration(value); + _data.setDuration(value); + } + +public Q_SLOTS: + + //* remove widget from map + virtual bool unregisterWidget(QObject *object) + { + return _data.unregisterWidget(object); + } + +private: + //* data map + DataMap _data; +}; + +} // namespace + +#endif // ADWAITA_HEADER_VIEW_ENGINE_H diff --git a/src/lib/animations/adwaitascrollbardata.cpp b/src/lib/animations/adwaitascrollbardata.cpp new file mode 100644 index 0000000..760a702 --- /dev/null +++ b/src/lib/animations/adwaitascrollbardata.cpp @@ -0,0 +1,214 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitascrollbardata.h" + +#include +#include +#include + +Q_GUI_EXPORT QStyleOptionSlider qt_qscrollbarStyleOption(QScrollBar *); + +namespace Adwaita +{ + +//______________________________________________ +ScrollBarData::ScrollBarData(QObject *parent, QWidget *target, int duration) + : WidgetStateData(parent, target, duration) + , _position(-1, -1) +{ + target->installEventFilter(this); + + _addLineData._animation = new Animation(duration, this); + _subLineData._animation = new Animation(duration, this); + _grooveData._animation = new Animation(duration, this); + + connect(addLineAnimation().data(), SIGNAL(finished()), SLOT(clearAddLineRect())); + connect(subLineAnimation().data(), SIGNAL(finished()), SLOT(clearSubLineRect())); + + // setup animation + setupAnimation(addLineAnimation(), "addLineOpacity"); + setupAnimation(subLineAnimation(), "subLineOpacity"); + setupAnimation(grooveAnimation(), "grooveOpacity"); +} + +//______________________________________________ +bool ScrollBarData::eventFilter(QObject *object, QEvent *event) +{ + if (object != target().data()) { + return WidgetStateData::eventFilter(object, event); + } + + // check event type + switch (event->type()) { + case QEvent::HoverEnter: + setGrooveHovered(true); + grooveAnimation().data()->setDirection(Animation::Forward); + if (!grooveAnimation().data()->isRunning()) { + grooveAnimation().data()->start(); + } + case QEvent::HoverMove: + hoverMoveEvent(object, event); + break; + case QEvent::HoverLeave: + setGrooveHovered(false); + grooveAnimation().data()->setDirection(Animation::Backward); + if (!grooveAnimation().data()->isRunning()) { + grooveAnimation().data()->start(); + } + hoverLeaveEvent(object, event); + + break; + default: + break; + } + + return WidgetStateData::eventFilter(object, event); +} + +//______________________________________________ +const Animation::Pointer &ScrollBarData::animation(QStyle::SubControl subcontrol) const +{ + switch (subcontrol) { + case QStyle::SC_ScrollBarSlider: + return animation(); + case QStyle::SC_ScrollBarAddLine: + return addLineAnimation(); + case QStyle::SC_ScrollBarSubLine: + return subLineAnimation(); + case QStyle::SC_ScrollBarGroove: + return grooveAnimation(); + } + + return animation(); +} + +//______________________________________________ +qreal ScrollBarData::opacity(QStyle::SubControl subcontrol) const +{ + switch (subcontrol) { + case QStyle::SC_ScrollBarSlider: + return opacity(); + case QStyle::SC_ScrollBarAddLine: + return addLineOpacity(); + case QStyle::SC_ScrollBarSubLine: + return subLineOpacity(); + case QStyle::SC_ScrollBarGroove: + return grooveOpacity(); + } + + return 0; +} + +//______________________________________________ +void ScrollBarData::hoverMoveEvent(QObject *object, QEvent *event) +{ + // try cast object to scrollbar + QScrollBar *scrollBar(qobject_cast(object)); + if (!scrollBar || scrollBar->isSliderDown()) { + return; + } + + // retrieve scrollbar option + QStyleOptionSlider opt(qt_qscrollbarStyleOption(scrollBar)); + + // cast event + QHoverEvent *hoverEvent = static_cast(event); + QStyle::SubControl hoverControl = scrollBar->style()->hitTestComplexControl(QStyle::CC_ScrollBar, &opt, hoverEvent->pos(), scrollBar); + + // update hover state + updateAddLineArrow(hoverControl); + updateSubLineArrow(hoverControl); + + // store position + _position = hoverEvent->pos(); +} + +//______________________________________________ +void ScrollBarData::hoverLeaveEvent(QObject *, QEvent *) +{ + // reset hover state + updateSubLineArrow(QStyle::SC_None); + updateAddLineArrow(QStyle::SC_None); + + // reset mouse position + _position = QPoint(-1, -1); +} + +//_____________________________________________________________________ +void ScrollBarData::updateSubLineArrow(QStyle::SubControl hoverControl) +{ + if (hoverControl == QStyle::SC_ScrollBarSubLine) { + if (!subLineArrowHovered()) { + setSubLineArrowHovered(true); + if (enabled()) { + subLineAnimation().data()->setDirection(Animation::Forward); + if (!subLineAnimation().data()->isRunning()) { + subLineAnimation().data()->start(); + } + } else { + setDirty(); + } + } + } else { + if (subLineArrowHovered()) { + setSubLineArrowHovered(false); + if (enabled()) { + subLineAnimation().data()->setDirection(Animation::Backward); + if (!subLineAnimation().data()->isRunning()) { + subLineAnimation().data()->start(); + } + } else { + setDirty(); + } + } + } +} + +//_____________________________________________________________________ +void ScrollBarData::updateAddLineArrow(QStyle::SubControl hoverControl) +{ + if (hoverControl == QStyle::SC_ScrollBarAddLine) { + if (!addLineArrowHovered()) { + setAddLineArrowHovered(true); + if (enabled()) { + addLineAnimation().data()->setDirection(Animation::Forward); + if (!addLineAnimation().data()->isRunning()) { + addLineAnimation().data()->start(); + } + } else { + setDirty(); + } + } + } else { + if (addLineArrowHovered()) { + setAddLineArrowHovered(false); + if (enabled()) { + addLineAnimation().data()->setDirection(Animation::Backward); + if (!addLineAnimation().data()->isRunning()) { + addLineAnimation().data()->start(); + } + } else { + setDirty(); + } + } + } +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitascrollbardata.h b/src/lib/animations/adwaitascrollbardata.h new file mode 100644 index 0000000..c3bc97a --- /dev/null +++ b/src/lib/animations/adwaitascrollbardata.h @@ -0,0 +1,292 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_SCROLLBAR_DATA_H +#define ADWAITA_SCROLLBAR_DATA_H + +#include "adwaitawidgetstatedata.h" +#include "adwaitaqt_export.h" + +#include + +namespace Adwaita +{ +//* scrollbar data +class ADWAITAQT_EXPORT ScrollBarData : public WidgetStateData +{ + Q_OBJECT + Q_PROPERTY(qreal addLineOpacity READ addLineOpacity WRITE setAddLineOpacity) + Q_PROPERTY(qreal subLineOpacity READ subLineOpacity WRITE setSubLineOpacity) + Q_PROPERTY(qreal grooveOpacity READ grooveOpacity WRITE setGrooveOpacity) +public: + //* constructor + ScrollBarData(QObject *parent, QWidget *target, int); + + //* destructor + virtual ~ScrollBarData() + { + } + + //* event filter + virtual bool eventFilter(QObject *, QEvent *); + + //* needed to avoid warning about virtual function being hidden + using WidgetStateData::animation; + using WidgetStateData::opacity; + + //* return animation for a given subcontrol + virtual const Animation::Pointer &animation(QStyle::SubControl) const; + + //* return default opacity for a given subcontrol + virtual qreal opacity(QStyle::SubControl) const; + + //* return default opacity for a given subcontrol + virtual bool isHovered(QStyle::SubControl control) const + { + switch (control) { + case QStyle::SC_ScrollBarAddLine: + return addLineArrowHovered(); + case QStyle::SC_ScrollBarSubLine: + return subLineArrowHovered(); + case QStyle::SC_ScrollBarGroove: + return grooveHovered(); + default: + return false; + } + } + + //* subControlRect + virtual QRect subControlRect(QStyle::SubControl control) const + { + switch (control) { + case QStyle::SC_ScrollBarAddLine: + return _addLineData._rect; + case QStyle::SC_ScrollBarSubLine: + return _subLineData._rect; + default: + return QRect(); + } + } + + //* subcontrol rect + virtual void setSubControlRect(QStyle::SubControl control, const QRect &rect) + { + switch (control) { + case QStyle::SC_ScrollBarAddLine: + _addLineData._rect = rect; + break; + case QStyle::SC_ScrollBarSubLine: + _subLineData._rect = rect; + break; + default: + break; + } + } + + //* duration + virtual void setDuration(int duration) + { + WidgetStateData::setDuration(duration); + addLineAnimation().data()->setDuration(duration); + subLineAnimation().data()->setDuration(duration); + grooveAnimation().data()->setDuration(duration); + } + + //* addLine opacity + virtual void setAddLineOpacity(qreal value) + { + value = digitize(value); + if (_addLineData._opacity == value) { + return; + } + _addLineData._opacity = value; + setDirty(); + } + + //* addLine opacity + virtual qreal addLineOpacity() const + { + return _addLineData._opacity; + } + + //* subLine opacity + virtual void setSubLineOpacity(qreal value) + { + value = digitize(value); + if (_subLineData._opacity == value) { + return; + } + _subLineData._opacity = value; + setDirty(); + } + + //* subLine opacity + virtual qreal subLineOpacity() const + { + return _subLineData._opacity; + } + + //* groove opacity + virtual void setGrooveOpacity(qreal value) + { + value = digitize(value); + if (_grooveData._opacity == value) { + return; + } + _grooveData._opacity = value; + setDirty(); + } + + //* groove opacity + virtual qreal grooveOpacity() const + { + return _grooveData._opacity; + } + + //* mouse position + QPoint position() const + { + return _position; + } + +protected Q_SLOTS: + + //* clear addLineRect + void clearAddLineRect() + { + if (addLineAnimation().data()->direction() == Animation::Backward) { + _addLineData._rect = QRect(); + } + } + + //* clear subLineRect + void clearSubLineRect() + { + if (subLineAnimation().data()->direction() == Animation::Backward) { + _subLineData._rect = QRect(); + } + } + +protected: + //* hoverMoveEvent + virtual void hoverMoveEvent(QObject *, QEvent *); + + //* hoverMoveEvent + virtual void hoverLeaveEvent(QObject *, QEvent *); + + //*@name hover flags + //@{ + + virtual bool addLineArrowHovered() const + { + return _addLineData._hovered; + } + + virtual void setAddLineArrowHovered(bool value) + { + _addLineData._hovered = value; + } + + virtual bool subLineArrowHovered() const + { + return _subLineData._hovered; + } + + virtual void setSubLineArrowHovered(bool value) + { + _subLineData._hovered = value; + } + + virtual bool grooveHovered() const + { + return _grooveData._hovered; + } + + virtual void setGrooveHovered(bool value) + { + _grooveData._hovered = value; + } + + //@} + + //* update add line arrow + virtual void updateAddLineArrow(QStyle::SubControl); + + //* update sub line arrow + virtual void updateSubLineArrow(QStyle::SubControl); + + //*@name timelines + //@{ + + virtual const Animation::Pointer &addLineAnimation() const + { + return _addLineData._animation; + } + + virtual const Animation::Pointer &subLineAnimation() const + { + return _subLineData._animation; + } + + virtual const Animation::Pointer &grooveAnimation() const + { + return _grooveData._animation; + } + +private: + //* stores sub control data + class Data + { + public: + //* constructor + Data() + : _hovered(false) + , _opacity(AnimationData::OpacityInvalid) + { + } + + //* true if hovered + bool _hovered; + + //* animation + Animation::Pointer _animation; + + //* opacity + qreal _opacity; + + //* rect + QRect _rect; + }; + + //* add line data (down arrow) + Data _addLineData; + + //* subtract line data (up arrow) + Data _subLineData; + + //* groove data + Data _grooveData; + + //* mouse position + QPoint _position; +}; + +} // namespace Adwaita + +#endif // ADWAITA_SCROLLBAR_DATA_H diff --git a/src/lib/animations/adwaitascrollbarengine.cpp b/src/lib/animations/adwaitascrollbarengine.cpp new file mode 100644 index 0000000..9b2e398 --- /dev/null +++ b/src/lib/animations/adwaitascrollbarengine.cpp @@ -0,0 +1,92 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitascrollbarengine.h" + +#include + +namespace Adwaita +{ + +//____________________________________________________________ +bool ScrollBarEngine::registerWidget(QWidget *widget, AnimationModes mode) +{ + // check widget + if (!widget) return false; + + // only handle hover and focus + if (mode & AnimationHover && !dataMap(AnimationHover).contains(widget)) { + dataMap(AnimationHover).insert(widget, new ScrollBarData(this, widget, duration()), enabled()); + } + if (mode & AnimationFocus && !dataMap(AnimationFocus).contains(widget)) { + dataMap(AnimationFocus).insert(widget, new WidgetStateData(this, widget, duration()), enabled()); + } + + // connect destruction signal + connect(widget, SIGNAL(destroyed(QObject *)), this, SLOT(unregisterWidget(QObject *)), Qt::UniqueConnection); + + return true; +} + +//____________________________________________________________ +bool ScrollBarEngine::isAnimated(const QObject *object, AnimationMode mode, QStyle::SubControl control) +{ + if (mode == AnimationHover) { + if (DataMap::Value data = this->data(object, AnimationHover)) { + const ScrollBarData *scrollBarData(static_cast(data.data())); + Animation::Pointer animation = scrollBarData->animation(control); + return animation.data()->isRunning(); + } else { + return false; + } + } else if (control == QStyle::SC_ScrollBarSlider) { + return WidgetStateEngine::isAnimated(object, mode); + } else { + return false; + } +} + +//____________________________________________________________ +AnimationMode ScrollBarEngine::animationMode(const QObject *object, QStyle::SubControl control) +{ + + // enable state + if (isAnimated(object, AnimationHover, control)) { + return AnimationHover; + } else if (isAnimated(object, AnimationFocus, control)) { + return AnimationFocus; + } else if (isAnimated(object, AnimationPressed, control)) { + return AnimationPressed; + } else { + return AnimationNone; + } +} + +//____________________________________________________________ +qreal ScrollBarEngine::opacity(const QObject *object, QStyle::SubControl control) +{ + if (isAnimated(object, AnimationHover, control)) { + return static_cast(data(object, AnimationHover).data())->opacity(control); + } else if (control == QStyle::SC_ScrollBarSlider) { + return WidgetStateEngine::buttonOpacity(object); + } + return AnimationData::OpacityInvalid; +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitascrollbarengine.h b/src/lib/animations/adwaitascrollbarengine.h new file mode 100644 index 0000000..fa6f33b --- /dev/null +++ b/src/lib/animations/adwaitascrollbarengine.h @@ -0,0 +1,112 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_SCROLLBAR_ENGINE_H +#define ADWAITA_SCROLLBAR_ENGINE_H + +#include "adwaitascrollbardata.h" +#include "adwaitawidgetstateengine.h" +#include "adwaitaqt_export.h" + +namespace Adwaita +{ +//* stores scrollbar hovered action and timeLine +class ADWAITAQT_EXPORT ScrollBarEngine : public WidgetStateEngine +{ + Q_OBJECT + +public: + //* constructor + explicit ScrollBarEngine(QObject *parent) + : WidgetStateEngine(parent) + { + } + + //* destructor + virtual ~ScrollBarEngine() + { + } + + //* register scrollbar + virtual bool registerWidget(QWidget *, AnimationModes); + + //*@name accessors + //@{ + + using WidgetStateEngine::isAnimated; + using WidgetStateEngine::opacity; + + //* true if widget is animated + virtual bool isAnimated(const QObject *, AnimationMode, QStyle::SubControl control); + + //* true if widget is animated + virtual AnimationMode animationMode(const QObject *object, QStyle::SubControl control); + + //* animation opacity + virtual qreal opacity(const QObject *object, QStyle::SubControl control); + + //* return true if given subcontrol is hovered + virtual bool isHovered(const QObject *object, QStyle::SubControl control) + { + if (DataMap::Value data = this->data(object, AnimationHover)) { + return static_cast(data.data())->isHovered(control); + } else { + return false; + } + } + + //* control rect associated to object + virtual QRect subControlRect(const QObject *object, QStyle::SubControl control) + { + if (DataMap::Value data = this->data(object, AnimationHover)) { + return static_cast(data.data())->subControlRect(control); + } else { + return QRect(); + } + } + + //* mouse position + virtual QPoint position(const QObject *object) + { + if (DataMap::Value data = this->data(object, AnimationHover)) { + return static_cast(data.data())->position(); + } else { + return QPoint(-1, -1); + } + } + + //@} + + //*@name modifiers + //@{ + + //* control rect + virtual void setSubControlRect(const QObject *object, QStyle::SubControl control, const QRect &rect) + { + if (DataMap::Value data = this->data(object, AnimationHover)) { + static_cast(data.data())->setSubControlRect(control, rect); + } + } + + //@} +}; + +} // namespace Adwaita + +#endif // ADWAITA_SCROLLBAR_ENGINE_H diff --git a/src/lib/animations/adwaitaspinboxdata.cpp b/src/lib/animations/adwaitaspinboxdata.cpp new file mode 100644 index 0000000..9ceed3b --- /dev/null +++ b/src/lib/animations/adwaitaspinboxdata.cpp @@ -0,0 +1,64 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitaspinboxdata.h" + +namespace Adwaita +{ + +//________________________________________________ +SpinBoxData::SpinBoxData(QObject *parent, QWidget *target, int duration) + : AnimationData(parent, target) +{ + _upArrowData._hoverAnimation = new Animation(duration, this); + _downArrowData._hoverAnimation = new Animation(duration, this); + _upArrowData._pressedAnimation = new Animation(duration, this); + _downArrowData._pressedAnimation = new Animation(duration, this); + setupAnimation(upArrowAnimation(), "upArrowOpacity"); + setupAnimation(downArrowAnimation(), "downArrowOpacity"); + setupAnimation(upArrowPressedAnimation(), "upArrowPressed"); + setupAnimation(downArrowPressedAnimation(), "downArrowPressed"); +} + +//______________________________________________ +bool SpinBoxData::Data::updateState(bool value, bool pressed) +{ + bool change = false; + if (_hoverState != value) { + _hoverState = value; + _hoverAnimation.data()->setDirection((_hoverState) ? Animation::Forward : Animation::Backward); + if (!_hoverAnimation.data()->isRunning()) { + _hoverAnimation.data()->start(); + } + change = true; + } + + if (_pressedState != pressed) { + _pressedState = pressed; + _pressedAnimation.data()->setDirection((_pressedState) ? Animation::Forward : Animation::Backward); + if (!_pressedAnimation.data()->isRunning()) { + _pressedAnimation.data()->start(); + } + change = true; + } + + return change; +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitaspinboxdata.h b/src/lib/animations/adwaitaspinboxdata.h new file mode 100644 index 0000000..cbbb280 --- /dev/null +++ b/src/lib/animations/adwaitaspinboxdata.h @@ -0,0 +1,247 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_SPINBOX_DATA_H +#define ADWAITA_SPINBOX_DATA_H + +#include "adwaitaanimationdata.h" +#include "adwaitaqt_export.h" + +#include + +namespace Adwaita +{ +//* handles spinbox arrows hover +class ADWAITAQT_EXPORT SpinBoxData : public AnimationData +{ + Q_OBJECT + + //* declare opacity property + Q_PROPERTY(qreal upArrowOpacity READ upArrowOpacity WRITE setUpArrowOpacity) + Q_PROPERTY(qreal downArrowOpacity READ downArrowOpacity WRITE setDownArrowOpacity) + Q_PROPERTY(qreal upArrowPressed READ upArrowPressed WRITE setUpArrowPressed) + Q_PROPERTY(qreal downArrowPressed READ downArrowPressed WRITE setDownArrowPressed) +public: + //* constructor + SpinBoxData(QObject *, QWidget *, int); + + //* destructor + virtual ~SpinBoxData() + { + } + + //* animation state + virtual bool updateState(QStyle::SubControl subControl, bool value, bool pressed) + { + if (subControl == QStyle::SC_SpinBoxUp) { + return _upArrowData.updateState(value, pressed); + } else if (subControl == QStyle::SC_SpinBoxDown) { + return _downArrowData.updateState(value, pressed); + } else { + return false; + } + } + + //* animation state + virtual bool isAnimated(QStyle::SubControl subControl) const + { + return ((subControl == QStyle::SC_SpinBoxUp && upArrowAnimation().data()->isRunning()) || (subControl == QStyle::SC_SpinBoxDown && downArrowAnimation().data()->isRunning())); + } + + //* opacity + virtual qreal opacity(QStyle::SubControl subControl) const + { + if (subControl == QStyle::SC_SpinBoxUp) { + return upArrowOpacity(); + } else if (subControl == QStyle::SC_SpinBoxDown) { + return downArrowOpacity(); + } else { + return OpacityInvalid; + } + } + + //* opacity + virtual qreal pressed(QStyle::SubControl subControl) const + { + if (subControl == QStyle::SC_SpinBoxUp) { + return upArrowPressed(); + } else if (subControl == QStyle::SC_SpinBoxDown) { + return downArrowPressed(); + } else { + return OpacityInvalid; + } + } + + //* duration + virtual void setDuration(int duration) + { + upArrowAnimation().data()->setDuration(duration); + downArrowAnimation().data()->setDuration(duration); + } + + //*@name up arrow animation + //@{ + + //* opacity + qreal upArrowOpacity() const + { + return _upArrowData._opacity; + } + + //* opacity + void setUpArrowOpacity(qreal value) + { + value = digitize(value); + if (_upArrowData._opacity == value) { + return; + } + _upArrowData._opacity = value; + setDirty(); + } + + //* animation + Animation::Pointer upArrowAnimation() const + { + return _upArrowData._hoverAnimation; + } + + //@} + + //*@name down arrow animation + //@{ + + //* opacity + qreal downArrowOpacity() const + { + return _downArrowData._opacity; + } + + //* opacity + void setDownArrowOpacity(qreal value) + { + value = digitize(value); + if (_downArrowData._opacity == value) { + return; + } + + _downArrowData._opacity = value; + setDirty(); + } + + //* animation + Animation::Pointer downArrowAnimation() const + { + return _downArrowData._hoverAnimation; + } + + //*@name up arrow pressed animation + //@{ + + //* opacity + qreal upArrowPressed() const + { + return _upArrowData._pressed; + } + + //* opacity + void setUpArrowPressed(qreal value) + { + value = digitize(value); + if (_upArrowData._pressed == value) { + return; + } + _upArrowData._pressed = value; + setDirty(); + } + + //* animation + Animation::Pointer upArrowPressedAnimation() const + { + return _upArrowData._pressedAnimation; + } + + //*@name down arrow pressed animation + //@{ + + //* opacity + qreal downArrowPressed() const + { + return _downArrowData._pressed; + } + + //* opacity + void setDownArrowPressed(qreal value) + { + value = digitize(value); + if (_downArrowData._pressed == value) { + return; + } + + _downArrowData._pressed = value; + setDirty(); + } + + //* animation + Animation::Pointer downArrowPressedAnimation() const + { + return _downArrowData._pressedAnimation; + } + + //@} + +private: + //* container for needed animation data + class ADWAITAQT_EXPORT Data + { + public: + //* default constructor + Data() + : _hoverState(false) + , _pressedState(false) + , _opacity(0) + , _pressed(0) + { + } + + //* state + bool updateState(bool, bool); + + //* arrow state + bool _hoverState; + bool _pressedState; + + //* animation + Animation::Pointer _hoverAnimation; + Animation::Pointer _pressedAnimation; + + //* opacity + qreal _opacity; + qreal _pressed; + }; + + //* up arrow data + Data _upArrowData; + + //* down arrow data + Data _downArrowData; +}; + +} // namespace Adwaita + +#endif // ADWAITA_SPINBOX_DATA_H diff --git a/src/lib/animations/adwaitaspinboxengine.cpp b/src/lib/animations/adwaitaspinboxengine.cpp new file mode 100644 index 0000000..1035896 --- /dev/null +++ b/src/lib/animations/adwaitaspinboxengine.cpp @@ -0,0 +1,44 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitaspinboxengine.h" + +#include + +namespace Adwaita +{ + +//____________________________________________________________ +bool SpinBoxEngine::registerWidget(QWidget *widget) +{ + if (!widget) { + return false; + } + + // create new data class + if (!_data.contains(widget)) { + _data.insert(widget, new SpinBoxData(this, widget, duration()), enabled()); + } + + // connect destruction signal + connect(widget, SIGNAL(destroyed(QObject *)), this, SLOT(unregisterWidget(QObject *)), Qt::UniqueConnection); + return true; +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitaspinboxengine.h b/src/lib/animations/adwaitaspinboxengine.h new file mode 100644 index 0000000..dae66d5 --- /dev/null +++ b/src/lib/animations/adwaitaspinboxengine.h @@ -0,0 +1,118 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_SPINBOX_ENGINE_H +#define ADWAITA_SPINBOX_ENGINE_H + +#include "adwaitabaseengine.h" +#include "adwaitadatamap.h" +#include "adwaitaspinboxdata.h" +#include "adwaitaqt_export.h" + +namespace Adwaita +{ +//* handle spinbox arrows hover effect +class ADWAITAQT_EXPORT SpinBoxEngine : public BaseEngine +{ + Q_OBJECT +public: + //* constructor + explicit SpinBoxEngine(QObject *parent) + : BaseEngine(parent) + { + } + + //* destructor + virtual ~SpinBoxEngine() + { + } + + //* register widget + virtual bool registerWidget(QWidget *); + + //* state + virtual bool updateState(const QObject *object, QStyle::SubControl subControl, bool value, bool pressed) + { + if (DataMap::Value data = _data.find(object)) { + return data.data()->updateState(subControl, value, pressed); + } else { + return false; + } + } + + //* true if widget is animated + virtual bool isAnimated(const QObject *object, QStyle::SubControl subControl) + { + if (DataMap::Value data = _data.find(object)) { + return data.data()->isAnimated(subControl); + } else { + return false; + } + } + + //* animation opacity + virtual qreal opacity(const QObject *object, QStyle::SubControl subControl) + { + if (DataMap::Value data = _data.find(object)) { + return data.data()->opacity(subControl); + } else { + return AnimationData::OpacityInvalid; + } + } + + //* animation opacity + virtual qreal pressed(const QObject *object, QStyle::SubControl subControl) + { + if (DataMap::Value data = _data.find(object)) { + return data.data()->pressed(subControl); + } else { + return AnimationData::OpacityInvalid; + } + } + + //* enability + virtual void setEnabled(bool value) + { + BaseEngine::setEnabled(value); + _data.setEnabled(value); + } + + //* duration + virtual void setDuration(int value) + { + BaseEngine::setDuration(value); + _data.setDuration(value); + } + +public Q_SLOTS: + + //* remove widget from map + virtual bool unregisterWidget(QObject *object) + { + return _data.unregisterWidget(object); + } + +private: + //* data map + DataMap _data; +}; + +} // namespace Adwaita + +#endif // ADWAITA_SPINBOX_ENGINE_H diff --git a/src/lib/animations/adwaitastackedwidgetdata.cpp b/src/lib/animations/adwaitastackedwidgetdata.cpp new file mode 100644 index 0000000..c6c174f --- /dev/null +++ b/src/lib/animations/adwaitastackedwidgetdata.cpp @@ -0,0 +1,132 @@ +////////////////////////////////////////////////////////////////////////////// +// adwaitastackedwidgetdata.cpp +// data container for QStackedWidget transition +// ------------------- +// +// Copyright (c) 2009 Hugo Pereira Da Costa +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +////////////////////////////////////////////////////////////////////////////// + +#include "adwaitastackedwidgetdata.h" + +namespace Adwaita +{ + +//______________________________________________________ +StackedWidgetData::StackedWidgetData(QObject *parent, QStackedWidget *target, int duration) + : TransitionData(parent, target, duration) + , _target(target) + , _index(target->currentIndex()) +{ + // configure transition + connect(_target.data(), SIGNAL(destroyed()), SLOT(targetDestroyed())); + connect(_target.data(), SIGNAL(currentChanged(int)), SLOT(animate())); + + // disable focus + transition().data()->setAttribute(Qt::WA_NoMousePropagation, true); + transition().data()->setFlag(TransitionWidget::PaintOnWidget, true); + + setMaxRenderTime(50); +} + +//___________________________________________________________________ +bool StackedWidgetData::initializeAnimation() +{ + // check enability + if (!(_target && _target.data()->isVisible())) { + return false; + } + + // check index + if (_target.data()->currentIndex() == _index) { + return false; + } + + // do not animate if either index or currentIndex is not valid + // but update _index none the less + if (_target.data()->currentIndex() < 0 || _index < 0) { + _index = _target.data()->currentIndex(); + return false; + } + + // get old widget (matching _index) and initialize transition + if (QWidget *widget = _target.data()->widget(_index)) { + transition().data()->setOpacity(0); + startClock(); + transition().data()->setGeometry(widget->geometry()); + transition().data()->setStartPixmap(transition().data()->grab(widget)); + + _index = _target.data()->currentIndex(); + return !slow(); + } else { + _index = _target.data()->currentIndex(); + return false; + } +} + +//___________________________________________________________________ +bool StackedWidgetData::animate() +{ + // check enability + if (!enabled()) { + return false; + } + + // initialize animation + if (!initializeAnimation()) { + return false; + } + + // show transition widget + transition().data()->show(); + transition().data()->raise(); + transition().data()->animate(); + return true; +} + +//___________________________________________________________________ +void StackedWidgetData::finishAnimation() +{ + // disable updates on currentWidget + if (_target && _target.data()->currentWidget()) { + _target.data()->currentWidget()->setUpdatesEnabled(false); + } + + // hide transition + transition().data()->hide(); + + // reenable updates and repaint + if (_target && _target.data()->currentWidget()) { + _target.data()->currentWidget()->setUpdatesEnabled(true); + _target.data()->currentWidget()->repaint(); + } + + // invalidate start widget + transition().data()->resetStartPixmap(); +} + +//___________________________________________________________________ +void StackedWidgetData::targetDestroyed() +{ + setEnabled(false); + _target.clear(); +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitastackedwidgetdata.h b/src/lib/animations/adwaitastackedwidgetdata.h new file mode 100644 index 0000000..8b500ac --- /dev/null +++ b/src/lib/animations/adwaitastackedwidgetdata.h @@ -0,0 +1,75 @@ +////////////////////////////////////////////////////////////////////////////// +// adwaitastackedwidgetdata.h +// data container for QStackedWidget transition +// ------------------- +// +// Copyright (c) 2009 Hugo Pereira Da Costa +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +////////////////////////////////////////////////////////////////////////////// + +#ifndef ADWAITA_STACKEDWIDGET_DATA_H +#define ADWAITA_STACKEDWIDGET_DATA_H + +#include "adwaitatransitiondata.h" +#include "adwaitaqt_export.h" + +#include + +namespace Adwaita +{ +//! generic data +class ADWAITAQT_EXPORT StackedWidgetData : public TransitionData +{ + Q_OBJECT + +public: + //! constructor + StackedWidgetData(QObject *, QStackedWidget *, int); + + //! destructor + virtual ~StackedWidgetData() + { + } + +protected Q_SLOTS: + + //! initialize animation + virtual bool initializeAnimation(); + + //! animate + virtual bool animate(); + + //! finish animation + virtual void finishAnimation(); + + //! called when target is destroyed + virtual void targetDestroyed(); + +private: + //! target + WeakPointer _target; + + //! current index + int _index; +}; + +} // namespace Adwaita + +#endif // ADWAITA_STACKEDWIDGET_DATA_H diff --git a/src/lib/animations/adwaitastackedwidgetengine.cpp b/src/lib/animations/adwaitastackedwidgetengine.cpp new file mode 100644 index 0000000..bffcd68 --- /dev/null +++ b/src/lib/animations/adwaitastackedwidgetengine.cpp @@ -0,0 +1,50 @@ +////////////////////////////////////////////////////////////////////////////// +// adwaitastackedwidgetengine.cpp +// stores event filters and maps widgets to animations +// ------------------- +// +// Copyright (c) 2009 Hugo Pereira Da Costa +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +////////////////////////////////////////////////////////////////////////////// + +#include "adwaitastackedwidgetengine.h" + +namespace Adwaita +{ + +//____________________________________________________________ +bool StackedWidgetEngine::registerWidget(QStackedWidget *widget) +{ + if (!widget) { + return false; + } + + if (!_data.contains(widget)) { + _data.insert(widget, new StackedWidgetData(this, widget, duration()), enabled()); + } + + // connect destruction signal + disconnect(widget, SIGNAL(destroyed(QObject *)), this, SLOT(unregisterWidget(QObject *))); + connect(widget, SIGNAL(destroyed(QObject *)), this, SLOT(unregisterWidget(QObject *))); + + return true; +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitastackedwidgetengine.h b/src/lib/animations/adwaitastackedwidgetengine.h new file mode 100644 index 0000000..c534bcf --- /dev/null +++ b/src/lib/animations/adwaitastackedwidgetengine.h @@ -0,0 +1,85 @@ +////////////////////////////////////////////////////////////////////////////// +// adwaitastackedwidgetengine.h +// stores event filters and maps widgets to animations +// ------------------- +// +// Copyright (c) 2009 Hugo Pereira Da Costa +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +////////////////////////////////////////////////////////////////////////////// + +#ifndef ADWAITA_STACKEDWIDGET_ENGINE_H +#define ADWAITA_STACKEDWIDGET_ENGINE_H + +#include "adwaitabaseengine.h" +#include "adwaitadatamap.h" +#include "adwaitastackedwidgetdata.h" +#include "adwaitaqt_export.h" + +namespace Adwaita +{ +//! used for simple widgets +class ADWAITAQT_EXPORT StackedWidgetEngine : public BaseEngine +{ + Q_OBJECT +public: + //! constructor + explicit StackedWidgetEngine(QObject *parent) + : BaseEngine(parent) + { + } + + //! destructor + virtual ~StackedWidgetEngine() + { + } + + //! register widget + virtual bool registerWidget(QStackedWidget *); + + //! duration + virtual void setEnabled(bool value) + { + BaseEngine::setEnabled(value); + _data.setEnabled(value); + } + + //! duration + virtual void setDuration(int value) + { + BaseEngine::setDuration(value); + _data.setDuration(value); + } + +public Q_SLOTS: + + //! remove widget from map + virtual bool unregisterWidget(QObject *object) + { + return _data.unregisterWidget(object); + } + +private: + //! maps + DataMap _data; +}; + +} // namespace Adwaita + +#endif // ADWAITA_STACKEDWIDGET_ENGINE_H diff --git a/src/lib/animations/adwaitatabbardata.cpp b/src/lib/animations/adwaitatabbardata.cpp new file mode 100644 index 0000000..37da3bc --- /dev/null +++ b/src/lib/animations/adwaitatabbardata.cpp @@ -0,0 +1,156 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * 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 . * + *************************************************************************/ + +////////////////////////////////////////////////////////////////////////////// +// adwaitatabbardata.cpp +// data container for QTabBar animations +// ------------------- +// +// Copyright (c) 2009 Hugo Pereira Da Costa +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +////////////////////////////////////////////////////////////////////////////// + +#include "adwaitatabbardata.h" + +#include +#include + +namespace Adwaita +{ + +//______________________________________________ +TabBarData::TabBarData(QObject *parent, QWidget *target, int duration) + : AnimationData(parent, target) +{ + _current._animation = new Animation(duration, this); + setupAnimation(currentIndexAnimation(), "currentOpacity"); + currentIndexAnimation().data()->setDirection(Animation::Forward); + + _previous._animation = new Animation(duration, this); + setupAnimation(previousIndexAnimation(), "previousOpacity"); + previousIndexAnimation().data()->setDirection(Animation::Backward); +} + +//______________________________________________ +Animation::Pointer TabBarData::animation(const QPoint &position) const +{ + if (!enabled()) { + return Animation::Pointer(); + } + + const QTabBar *local(qobject_cast(target().data())); + if (!local) { + return Animation::Pointer(); + } + + int index(local->tabAt(position)); + if (index < 0) { + return Animation::Pointer(); + } else if (index == currentIndex()) { + return currentIndexAnimation(); + } else if (index == previousIndex()) { + return previousIndexAnimation(); + } else { + return Animation::Pointer(); + } +} + +//______________________________________________ +bool TabBarData::updateState(const QPoint &position, bool hovered) +{ + if (!enabled()) { + return false; + } + + const QTabBar *local(qobject_cast(target().data())); + if (!local) { + return false; + } + + int index(local->tabAt(position)); + if (index < 0) { + return false; + } + + if (hovered) { + if (index != currentIndex()) { + if (currentIndex() >= 0) { + setPreviousIndex(currentIndex()); + setCurrentIndex(-1); + previousIndexAnimation().data()->restart(); + } + + setCurrentIndex(index); + currentIndexAnimation().data()->restart(); + return true; + } else { + return false; + } + } else if (index == currentIndex()) { + setPreviousIndex(currentIndex()); + setCurrentIndex(-1); + previousIndexAnimation().data()->restart(); + return true; + } else { + return false; + } +} + +//______________________________________________ +qreal TabBarData::opacity(const QPoint &position) const +{ + if (!enabled()) { + return OpacityInvalid; + } + + const QTabBar *local(qobject_cast(target().data())); + if (!local) { + return OpacityInvalid; + } + + int index(local->tabAt(position)); + if (index < 0) { + return OpacityInvalid; + } else if (index == currentIndex()) { + return currentOpacity(); + } else if (index == previousIndex()) { + return previousOpacity(); + } else { + return OpacityInvalid; + } +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitatabbardata.h b/src/lib/animations/adwaitatabbardata.h new file mode 100644 index 0000000..032769b --- /dev/null +++ b/src/lib/animations/adwaitatabbardata.h @@ -0,0 +1,169 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_TABBAR_DATA_H +#define ADWAITA_TABBAR_DATA_H + +#include "adwaitaanimationdata.h" +#include "adwaitaqt_export.h" + +#include + +namespace Adwaita +{ +//* tabbars +class ADWAITAQT_EXPORT TabBarData : public AnimationData +{ + Q_OBJECT + + //* declare opacity property + Q_PROPERTY(qreal currentOpacity READ currentOpacity WRITE setCurrentOpacity) + Q_PROPERTY(qreal previousOpacity READ previousOpacity WRITE setPreviousOpacity) +public: + //* constructor + TabBarData(QObject *parent, QWidget *target, int duration); + + //* destructor + virtual ~TabBarData() + { + } + + //* duration + void setDuration(int duration) + { + currentIndexAnimation().data()->setDuration(duration); + previousIndexAnimation().data()->setDuration(duration); + } + + //* update state + bool updateState(const QPoint &, bool); + + //*@name current index handling + //@{ + + //* current opacity + virtual qreal currentOpacity() const + { + return _current._opacity; + } + + //* current opacity + virtual void setCurrentOpacity(qreal value) + { + if (_current._opacity == value) { + return; + } + + _current._opacity = value; + setDirty(); + } + + //* current index + virtual int currentIndex() const + { + return _current._index; + } + + //* current index + virtual void setCurrentIndex(int index) + { + _current._index = index; + } + + //* current index animation + virtual const Animation::Pointer ¤tIndexAnimation() const + { + return _current._animation; + } + + //@} + + //*@name previous index handling + //@{ + + //* previous opacity + virtual qreal previousOpacity() const + { + return _previous._opacity; + } + + //* previous opacity + virtual void setPreviousOpacity(qreal value) + { + if (_previous._opacity == value) { + return; + } + + _previous._opacity = value; + setDirty(); + } + + //* previous index + virtual int previousIndex() const + { + return _previous._index; + } + + //* previous index + virtual void setPreviousIndex(int index) + { + _previous._index = index; + } + + //* previous index Animation + virtual const Animation::Pointer &previousIndexAnimation() const + { + return _previous._animation; + } + + //@} + + //* return Animation associated to action at given position, if any + virtual Animation::Pointer animation(const QPoint &position) const; + + //* return opacity associated to action at given position, if any + virtual qreal opacity(const QPoint &position) const; + +private: + //* container for needed animation data + class Data + { + public: + //* default constructor + Data() + : _opacity(0) + , _index(-1) + { + } + + Animation::Pointer _animation; + qreal _opacity; + int _index; + }; + + //* current tab animation data (for hover enter animations) + Data _current; + + //* previous tab animations data (for hover leave animations) + Data _previous; +}; + +} // namespace Adwaita + +#endif // ADWAITA_TABBAR_DATA_H diff --git a/src/lib/animations/adwaitatabbarengine.cpp b/src/lib/animations/adwaitatabbarengine.cpp new file mode 100644 index 0000000..5ee8fbf --- /dev/null +++ b/src/lib/animations/adwaitatabbarengine.cpp @@ -0,0 +1,75 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitatabbarengine.h" + +#include + +namespace Adwaita +{ + +//____________________________________________________________ +bool TabBarEngine::registerWidget(QWidget *widget) +{ + if (!widget) { + return false; + } + + // create new data class + if (!_hoverData.contains(widget)) { + _hoverData.insert(widget, new TabBarData(this, widget, duration()), enabled()); + } + + if (!_focusData.contains(widget)) { + _focusData.insert(widget, new TabBarData(this, widget, duration()), enabled()); + } + + // connect destruction signal + connect(widget, SIGNAL(destroyed(QObject *)), this, SLOT(unregisterWidget(QObject *)), Qt::UniqueConnection); + return true; +} + +//____________________________________________________________ +bool TabBarEngine::updateState(const QObject *object, const QPoint &position, AnimationMode mode, bool value) +{ + DataMap::Value data(TabBarEngine::data(object, mode)); + return (data && data.data()->updateState(position, value)); +} + +//____________________________________________________________ +bool TabBarEngine::isAnimated(const QObject *object, const QPoint &position, AnimationMode mode) +{ + DataMap::Value data(TabBarEngine::data(object, mode)); + return (data && data.data()->animation(position) && data.data()->animation(position).data()->isRunning()); +} + +//____________________________________________________________ +DataMap::Value TabBarEngine::data(const QObject *object, AnimationMode mode) +{ + switch (mode) { + case AnimationHover: + return _hoverData.find(object).data(); + case AnimationFocus: + return _focusData.find(object).data(); + default: + return DataMap::Value(); + } +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitatabbarengine.h b/src/lib/animations/adwaitatabbarengine.h new file mode 100644 index 0000000..a33984f --- /dev/null +++ b/src/lib/animations/adwaitatabbarengine.h @@ -0,0 +1,110 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_TABBAR_ENGINE_H +#define ADWAITA_TABBAR_ENGINE_H + +#include "adwaita.h" +#include "adwaitabaseengine.h" +#include "adwaitadatamap.h" +#include "adwaitatabbardata.h" +#include "adwaitaqt_export.h" + +namespace Adwaita +{ +//* stores tabbar hovered action and timeLine +class ADWAITAQT_EXPORT TabBarEngine : public BaseEngine +{ + Q_OBJECT +public: + //* constructor + explicit TabBarEngine(QObject *parent) + : BaseEngine(parent) + { + } + + //* destructor + virtual ~TabBarEngine() + { + } + + //* register tabbar + virtual bool registerWidget(QWidget *); + + //* true if widget hover state is changed + virtual bool updateState(const QObject *, const QPoint &, AnimationMode, bool); + + //* true if widget is animated + virtual bool isAnimated(const QObject *object, const QPoint &point, AnimationMode); + + //* animation opacity + virtual qreal opacity(const QObject *object, const QPoint &point, AnimationMode mode) + { + return isAnimated(object, point, mode) ? data(object, mode).data()->opacity(point) : AnimationData::OpacityInvalid; + } + + //* enability + virtual void setEnabled(bool value) + { + BaseEngine::setEnabled(value); + _hoverData.setEnabled(value); + _focusData.setEnabled(value); + } + + //* duration + virtual void setDuration(int value) + { + BaseEngine::setDuration(value); + _hoverData.setDuration(value); + _focusData.setDuration(value); + } + +public Q_SLOTS: + + //* remove widget from map + virtual bool unregisterWidget(QObject *object) + { + if (!object) { + return false; + } + + bool found = false; + if (_hoverData.unregisterWidget(object)) { + found = true; + } + + if (_focusData.unregisterWidget(object)) { + found = true; + } + + return found; + } + +private: + //* returns data associated to widget + DataMap::Value data(const QObject *, AnimationMode); + + //* data map + DataMap _hoverData; + DataMap _focusData; +}; + +} // namespace Adwaita + +#endif // ADWAITA_TABBAR_ENGINE_H diff --git a/src/lib/animations/adwaitatoolboxengine.cpp b/src/lib/animations/adwaitatoolboxengine.cpp new file mode 100644 index 0000000..46509a4 --- /dev/null +++ b/src/lib/animations/adwaitatoolboxengine.cpp @@ -0,0 +1,55 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitatoolboxengine.h" + +namespace Adwaita +{ + +//____________________________________________________________ +bool ToolBoxEngine::registerWidget(QWidget *widget) +{ + if (!widget) { + return false; + } + + if (!_data.contains(widget)) { + _data.insert(widget, new WidgetStateData(this, widget, duration()), enabled()); + } + + // connect destruction signal + connect(widget, SIGNAL(destroyed(QObject *)), this, SLOT(unregisterWidget(QObject *)), Qt::UniqueConnection); + return true; +} + +//____________________________________________________________ +bool ToolBoxEngine::updateState(const QPaintDevice *object, bool value) +{ + PaintDeviceDataMap::Value data(ToolBoxEngine::data(object)); + return (data && data.data()->updateState(value)); +} + +//____________________________________________________________ +bool ToolBoxEngine::isAnimated(const QPaintDevice *object) +{ + PaintDeviceDataMap::Value data(ToolBoxEngine::data(object)); + return (data && data.data()->animation() && data.data()->animation().data()->isRunning()); +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitatoolboxengine.h b/src/lib/animations/adwaitatoolboxengine.h new file mode 100644 index 0000000..2b49bf7 --- /dev/null +++ b/src/lib/animations/adwaitatoolboxengine.h @@ -0,0 +1,104 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_TOOLBOX_ENGINE_H +#define ADWAITA_TOOLBOX_ENGINE_H + +#include "adwaitabaseengine.h" +#include "adwaitadatamap.h" +#include "adwaitawidgetstatedata.h" +#include "adwaitaqt_export.h" + +namespace Adwaita +{ +//* QToolBox animation engine +class ADWAITAQT_EXPORT ToolBoxEngine : public BaseEngine +{ + Q_OBJECT + +public: + //* constructor + explicit ToolBoxEngine(QObject *parent) + : BaseEngine(parent) + { + } + + //* destructor + virtual ~ToolBoxEngine() + { + } + + //* enability + virtual void setEnabled(bool value) + { + BaseEngine::setEnabled(value); + _data.setEnabled(value); + } + + //* duration + virtual void setDuration(int value) + { + BaseEngine::setDuration(value); + _data.setDuration(value); + } + + //* register widget + virtual bool registerWidget(QWidget *); + + //* true if widget hover state is changed + virtual bool updateState(const QPaintDevice *, bool); + + //* true if widget is animated + virtual bool isAnimated(const QPaintDevice *); + + //* animation opacity + virtual qreal opacity(const QPaintDevice *object) + { + return isAnimated(object) ? data(object).data()->opacity() : AnimationData::OpacityInvalid; + } + +public Q_SLOTS: + + //* remove widget from map + virtual bool unregisterWidget(QObject *data) + { + if (!data) { + return false; + } + + // reinterpret_cast is safe here since only the address is used to find + // data in the map + return _data.unregisterWidget(reinterpret_cast(data)); + } + +protected: + //* returns data associated to widget + PaintDeviceDataMap::Value data(const QPaintDevice *object) + { + return _data.find(object).data(); + } + +private: + //* map + PaintDeviceDataMap _data; +}; + +} // namespace Adwaita + +#endif // ADWAITA_TOOLBOX_ENGINE_H diff --git a/src/lib/animations/adwaitatransitiondata.cpp b/src/lib/animations/adwaitatransitiondata.cpp new file mode 100644 index 0000000..3486752 --- /dev/null +++ b/src/lib/animations/adwaitatransitiondata.cpp @@ -0,0 +1,48 @@ +////////////////////////////////////////////////////////////////////////////// +// adwaitatransitiondata.cpp +// data container for generic transitions +// ------------------- +// +// Copyright (c) 2009 Hugo Pereira Da Costa +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +////////////////////////////////////////////////////////////////////////////// + +#include "adwaitatransitiondata.h" + +namespace Adwaita +{ + +//_________________________________________________________________ +TransitionData::TransitionData(QObject *parent, QWidget *target, int duration) + : QObject(parent) + , _transition(new TransitionWidget(target, duration)) +{ + _transition.data()->hide(); +} + +//_________________________________________________________________ +TransitionData::~TransitionData() +{ + if (_transition) { + _transition.data()->deleteLater(); + } +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitatransitiondata.h b/src/lib/animations/adwaitatransitiondata.h new file mode 100644 index 0000000..631069c --- /dev/null +++ b/src/lib/animations/adwaitatransitiondata.h @@ -0,0 +1,164 @@ +////////////////////////////////////////////////////////////////////////////// +// adwaitatransitiondata.h +// data container for generic transitions +// ------------------- +// +// Copyright (c) 2009 Hugo Pereira Da Costa +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +////////////////////////////////////////////////////////////////////////////// + +#ifndef ADWAITA_TRANSITION_DATA_H +#define ADWAITA_TRANSITION_DATA_H + +#include "adwaitatransitionwidget.h" +#include "adwaitaqt_export.h" + +#include +#include +#include + +namespace Adwaita +{ +//* generic data +class ADWAITAQT_EXPORT TransitionData : public QObject +{ + Q_OBJECT +public: + //* constructor + TransitionData(QObject *parent, QWidget *target, int); + + //* destructor + virtual ~TransitionData(); + + //* enability + virtual void setEnabled(bool value) + { + _enabled = value; + } + + //* enability + virtual bool enabled() const + { + return _enabled; + } + + //* duration + virtual void setDuration(int duration) + { + if (_transition) { + _transition.data()->setDuration(duration); + } + } + + //* max render time + void setMaxRenderTime(int value) + { + _maxRenderTime = value; + } + + //* max renderTime + const int &maxRenderTime() const + { + return _maxRenderTime; + } + + //* start clock + void startClock() + { + if (!_clock.isValid()) { + _clock.start(); + } else { + _clock.restart(); + } + } + + //* check if rendering is two slow + bool slow() const + { + return !(!_clock.isValid() || _clock.elapsed() <= maxRenderTime()); + } + +protected Q_SLOTS: + + //* initialize animation + virtual bool initializeAnimation() = 0; + + //* animate + virtual bool animate() = 0; + +protected: + //* returns true if one parent matches given class name + inline bool hasParent(const QWidget *, const char *) const; + + //* transition widget + virtual const TransitionWidget::Pointer &transition() const + { + return _transition; + } + + //* used to avoid recursion when grabbing widgets + void setRecursiveCheck(bool value) + { + _recursiveCheck = value; + } + + //* used to avoid recursion when grabbing widgets + bool recursiveCheck() const + { + return _recursiveCheck; + } + +private: + //* enability + bool _enabled = true; + + //* used to avoid recursion when grabbing widgets + bool _recursiveCheck = false; + + //* timer used to detect slow rendering + QElapsedTimer _clock; + + //* max render time + /*! used to detect slow rendering */ + int _maxRenderTime = 200; + + //* animation handling + TransitionWidget::Pointer _transition; +}; + +//_____________________________________________________________________________________ +bool TransitionData::hasParent(const QWidget *widget, const char *className) const +{ + if (!widget) { + return false; + } + + for (QWidget *parent = widget->parentWidget(); parent; parent = parent->parentWidget()) { + if (parent->inherits(className)) { + return true; + } + } + + return false; +} + +} // namespace Adwaita + +#endif // ADWAITA_TRANSITION_DATA_H diff --git a/src/lib/animations/adwaitatransitionwidget.cpp b/src/lib/animations/adwaitatransitionwidget.cpp new file mode 100644 index 0000000..b5e9ddf --- /dev/null +++ b/src/lib/animations/adwaitatransitionwidget.cpp @@ -0,0 +1,299 @@ +////////////////////////////////////////////////////////////////////////////// +// adwaitatransitionwidget.cpp +// stores event filters and maps widgets to transitions for transitions +// ------------------- +// +// Copyright (c) 2009 Hugo Pereira Da Costa +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +////////////////////////////////////////////////////////////////////////////// + +#include "adwaitatransitionwidget.h" + +#include +#include +#include +#include + +namespace Adwaita +{ + +//________________________________________________ +bool TransitionWidget::_paintEnabled = true; +bool TransitionWidget::paintEnabled() +{ + return _paintEnabled; +} + +int TransitionWidget::_steps = 0; + +//________________________________________________ +TransitionWidget::TransitionWidget(QWidget *parent, int duration): + QWidget(parent), + _animation(new Animation(duration, this)) +{ + // background flags + setAttribute(Qt::WA_NoSystemBackground); + setAutoFillBackground(false); + + // setup animation + _animation.data()->setStartValue(0); + _animation.data()->setEndValue(1.0); + _animation.data()->setTargetObject(this); + _animation.data()->setPropertyName("opacity"); + + // hide when animation is finished + connect(_animation.data(), SIGNAL(finished()), SLOT(hide())); +} + +//________________________________________________ +QPixmap TransitionWidget::grab(QWidget *widget, QRect rect) +{ + // change rect + if (!rect.isValid()) { + rect = widget->rect(); + } + + if (!rect.isValid()) { + return QPixmap(); + } + + // initialize pixmap + QPixmap out(rect.size()); + out.fill(Qt::transparent); + _paintEnabled = false; + + if (testFlag(GrabFromWindow)) { + rect = rect.translated(widget->mapTo(widget->window(), widget->rect().topLeft())); + widget = widget->window(); + out = widget->grab(rect); + } else { + if (!testFlag(Transparent)) { + grabBackground(out, widget, rect); + } + grabWidget(out, widget, rect); + } + + _paintEnabled = true; + + return out; +} + +//________________________________________________ +bool TransitionWidget::event(QEvent *event) +{ + switch (event->type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::KeyPress: + case QEvent::KeyRelease: + endAnimation(); + hide(); + event->ignore(); + return false; + default: + return QWidget::event(event); + } +} + +//________________________________________________ +void TransitionWidget::paintEvent(QPaintEvent *event) +{ + // fully transparent case + if (opacity() >= 1.0 && endPixmap().isNull()) { + return; + } + + if (!_paintEnabled) { + return; + } + + // get rect + QRect rect = event->rect(); + if (!rect.isValid()) { + rect = this->rect(); + } + + // local pixmap + bool paintOnWidget(testFlag(PaintOnWidget) && !testFlag(Transparent)); + if (!paintOnWidget) { + if (_currentPixmap.isNull() || _currentPixmap.size() != size()) { + _currentPixmap = QPixmap(size()); + } + } + + // fill + _currentPixmap.fill(Qt::transparent); + + // copy local pixmap to current + { + QPainter p; + + // draw end pixmap first, provided that opacity is small enough + if (opacity() >= 0.004 && !_endPixmap.isNull()) { + // faded endPixmap if parent target is transparent and opacity is + if (opacity() <= 0.996 && testFlag(Transparent)) { + fade(_endPixmap, _currentPixmap, opacity(), rect); + p.begin(&_currentPixmap); + p.setClipRect(event->rect()); + } else { + if (paintOnWidget) { + p.begin(this); + } else { + p.begin(&_currentPixmap); + } + p.setClipRect(event->rect()); + p.drawPixmap(QPoint(), _endPixmap); + } + } else { + if (paintOnWidget) { + p.begin(this); + } else { + p.begin(&_currentPixmap); + } + p.setClipRect(event->rect()); + } + + // draw fading start pixmap + if (opacity() <= 0.996 && !_startPixmap.isNull()) { + if (opacity() >= 0.004) { + fade(_startPixmap, _localStartPixmap, 1.0 - opacity(), rect); + p.drawPixmap(QPoint(), _localStartPixmap); + } else { + p.drawPixmap(QPoint(), _startPixmap); + } + } + + p.end(); + } + + // copy current pixmap on widget + if (!paintOnWidget) { + QPainter p(this); + p.setClipRect(event->rect()); + p.drawPixmap(QPoint(0, 0), _currentPixmap); + p.end(); + } +} + +//________________________________________________ +void TransitionWidget::grabBackground(QPixmap &pixmap, QWidget *widget, QRect &rect) const +{ + if (!widget) { + return; + } + + QWidgetList widgets; + if (widget->autoFillBackground()) { + widgets.append(widget); + } + + QWidget *parent(0); + + // get highest level parent + for (parent = widget->parentWidget(); parent; parent = parent->parentWidget()) { + if (!(parent->isVisible() && parent->rect().isValid())) { + continue; + } + + // store in list + widgets.append(parent); + + // stop at topLevel + if (parent->isTopLevel() || parent->autoFillBackground()) { + break; + } + } + + if (!parent) { + parent = widget; + } + + // painting + QPainter p(&pixmap); + p.setClipRect(rect); + QBrush backgroundBrush = parent->palette().brush(parent->backgroundRole()); + if (backgroundBrush.style() == Qt::TexturePattern) { + p.drawTiledPixmap(rect, backgroundBrush.texture(), widget->mapTo(parent, rect.topLeft())); + } else { + p.fillRect(pixmap.rect(), backgroundBrush); + } + + if (parent->isTopLevel() && parent->testAttribute(Qt::WA_StyledBackground)) { + QStyleOption option; + option.initFrom(parent); + option.rect = rect; + option.rect.translate(widget->mapTo(parent, rect.topLeft())); + p.translate(-option.rect.topLeft()); + parent->style()->drawPrimitive(QStyle::PE_Widget, &option, &p, parent); + p.translate(option.rect.topLeft()); + } + + // draw all widgets in parent list + // backward + QPaintEvent event(rect); + for (int i = widgets.size() - 1; i >= 0; i--) { + QWidget *w = widgets.at(i); + w->render(&p, -widget->mapTo(w, rect.topLeft()), rect, 0); + } + + // end + p.end(); +} + +//________________________________________________ +void TransitionWidget::grabWidget(QPixmap &pixmap, QWidget *widget, QRect &rect) const +{ + widget->render(&pixmap, pixmap.rect().topLeft(), rect, QWidget::DrawChildren); +} + +//________________________________________________ +void TransitionWidget::fade(const QPixmap &source, QPixmap &target, qreal opacity, const QRect &rect) const +{ + if (target.isNull() || target.size() != size()) { + target = QPixmap(size()); + } + + // erase target + target.fill(Qt::transparent); + + // check opacity + if (opacity * 255 < 1) { + return; + } + + QPainter p(&target); + p.setClipRect(rect); + + // draw pixmap + p.drawPixmap(QPoint(0, 0), source); + + // opacity mask (0.996 corresponds to 254/255) + if (opacity <= 0.996) { + p.setCompositionMode(QPainter::CompositionMode_DestinationIn); + QColor color(Qt::black); + color.setAlphaF(opacity); + p.fillRect(rect, color); + } + + p.end(); + return; +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitatransitionwidget.h b/src/lib/animations/adwaitatransitionwidget.h new file mode 100644 index 0000000..e77f487 --- /dev/null +++ b/src/lib/animations/adwaitatransitionwidget.h @@ -0,0 +1,264 @@ +////////////////////////////////////////////////////////////////////////////// +// adwaitatransitionwidget.h +// stores event filters and maps widgets to transitions for transitions +// ------------------- +// +// Copyright (c) 2009 Hugo Pereira Da Costa +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +////////////////////////////////////////////////////////////////////////////// + +#ifndef ADWAITA_TRANSITION_WIDGET_H +#define ADWAITA_TRANSITION_WIDGET_H + +#include "adwaita.h" +#include "adwaitaanimation.h" +#include "adwaitaqt_export.h" + +#include + +#include + +namespace Adwaita +{ +//* temporary widget used to perform smooth transition between one widget state and another +class ADWAITAQT_EXPORT TransitionWidget : public QWidget +{ + Q_OBJECT + + //* declare opacity property + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity) +public: + //* shortcut to painter + typedef WeakPointer Pointer; + + //* constructor + TransitionWidget(QWidget *parent, int duration); + + //* destructor + virtual ~TransitionWidget() = default; + + //*@name flags + //@{ + enum Flag { None = 0, GrabFromWindow = 1 << 0, Transparent = 1 << 1, PaintOnWidget = 1 << 2 }; + + Q_DECLARE_FLAGS(Flags, Flag) + + void setFlags(Flags value) + { + _flags = value; + } + + void setFlag(Flag flag, bool value = true) + { + if (value) { + _flags |= flag; + } else { + _flags &= (~flag); + } + } + + bool testFlag(Flag flag) const + { + return _flags.testFlag(flag); + } + + //@} + + //* duration + void setDuration(int duration) + { + if (_animation) { + _animation.data()->setDuration(duration); + } + } + + //* duration + int duration() const + { + return (_animation) ? _animation.data()->duration() : 0; + } + + //* steps + static void setSteps(int value) + { + _steps = value; + } + + //*@name opacity + //@{ + + virtual qreal opacity() const + { + return _opacity; + } + + virtual void setOpacity(qreal value) + { + value = digitize(value); + if (_opacity == value) { + return; + } + + _opacity = value; + update(); + } + + //@} + + //@name pixmaps handling + //@{ + + //* start + void resetStartPixmap() + { + setStartPixmap(QPixmap()); + } + + //* start + void setStartPixmap(QPixmap pixmap) + { + _startPixmap = pixmap; + } + + //* start + const QPixmap &startPixmap() const + { + return _startPixmap; + } + + //* end + void resetEndPixmap() + { + setEndPixmap(QPixmap()); + } + + //* end + void setEndPixmap(QPixmap pixmap) + { + _endPixmap = pixmap; + _currentPixmap = pixmap; + } + + //* start + const QPixmap &endPixmap() const + { + return _endPixmap; + } + + //* current + const QPixmap ¤tPixmap() const + { + return _currentPixmap; + } + + //@} + + //* grap pixmap + QPixmap grab(QWidget * = 0, QRect = QRect()); + + //* true if animated + virtual bool isAnimated() const + { + return _animation.data()->isRunning(); + } + + //* end animation + virtual void endAnimation() + { + if (_animation.data()->isRunning()) { + _animation.data()->stop(); + } + } + + //* animate transition + virtual void animate() + { + if (_animation.data()->isRunning()) { + _animation.data()->stop(); + } + _animation.data()->start(); + } + + //* true if paint is enabled + static bool paintEnabled(); + +protected: + //* generic event filter + virtual bool event(QEvent *); + + //* paint event + virtual void paintEvent(QPaintEvent *); + + //* grab widget background + /*! + Background is not rendered properly using QWidget::render. + Use home-made grabber instead. This is directly inspired from bespin. + Copyright (C) 2007 Thomas Luebking + */ + virtual void grabBackground(QPixmap &, QWidget *, QRect &) const; + + //* grab widget + virtual void grabWidget(QPixmap &, QWidget *, QRect &) const; + + //* fade pixmap + virtual void fade(const QPixmap &source, QPixmap &target, qreal opacity, const QRect &) const; + + //* apply step + virtual qreal digitize(const qreal &value) const + { + if (_steps > 0) { + return std::floor(value * _steps) / _steps; + } else { + return value; + } + } + +private: + //* Flags + Flags _flags = None; + + //* paint enabled + static bool _paintEnabled; + + //* internal transition animation + Animation::Pointer _animation; + + //* animation starting pixmap + QPixmap _startPixmap; + + //* animation starting pixmap + QPixmap _localStartPixmap; + + //* animation starting pixmap + QPixmap _endPixmap; + + //* current pixmap + QPixmap _currentPixmap; + + //* current state opacity + qreal _opacity = 0; + + //* steps + static int _steps; +}; + +} // namespace Adwaita + +#endif // ADWAITA_TRANSITION_WIDGET_H diff --git a/src/lib/animations/adwaitawidgetstatedata.cpp b/src/lib/animations/adwaitawidgetstatedata.cpp new file mode 100644 index 0000000..7d9d6e4 --- /dev/null +++ b/src/lib/animations/adwaitawidgetstatedata.cpp @@ -0,0 +1,44 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitawidgetstatedata.h" + +namespace Adwaita +{ + +//______________________________________________ +bool WidgetStateData::updateState(bool value) +{ + if (!_initialized) { + _state = value; + _initialized = true; + return false; + } else if (_state == value) { + return false; + } else { + _state = value; + animation().data()->setDirection(_state ? Animation::Forward : Animation::Backward); + if (!animation().data()->isRunning()) { + animation().data()->start(); + } + return true; + } +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitawidgetstatedata.h b/src/lib/animations/adwaitawidgetstatedata.h new file mode 100644 index 0000000..bb7ebf1 --- /dev/null +++ b/src/lib/animations/adwaitawidgetstatedata.h @@ -0,0 +1,59 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_WIDGET_STATE_DATA_H +#define ADWAITA_WIDGET_STATE_DATA_H + +#include "adwaitagenericdata.h" +#include "adwaitaqt_export.h" + +namespace Adwaita +{ +//* handle widget state (hover/focus/enable) changes +class ADWAITAQT_EXPORT WidgetStateData : public GenericData +{ + Q_OBJECT +public: + //* constructor + WidgetStateData(QObject *parent, QWidget *target, int duration, bool state = false) + : GenericData(parent, target, duration) + , _initialized(false) + , _state(state) + { + } + + //* destructor + virtual ~WidgetStateData() + { + } + + /** + returns true if hover has Changed + and starts timer accordingly + */ + virtual bool updateState(bool value); + +private: + bool _initialized; + bool _state; +}; + +} // namespace Adwaita + +#endif // ADWAITA_WIDGET_STATE_DATA_H diff --git a/src/lib/animations/adwaitawidgetstateengine.cpp b/src/lib/animations/adwaitawidgetstateengine.cpp new file mode 100644 index 0000000..854ef00 --- /dev/null +++ b/src/lib/animations/adwaitawidgetstateengine.cpp @@ -0,0 +1,136 @@ + +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitawidgetstateengine.h" + +#include "adwaitaenabledata.h" + +namespace Adwaita +{ + +//____________________________________________________________ +bool WidgetStateEngine::registerWidget(QWidget *widget, AnimationModes mode) +{ + if (!widget) { + return false; + } + + if (mode & AnimationHover && !_hoverData.contains(widget)) { + _hoverData.insert(widget, new WidgetStateData(this, widget, duration()), enabled()); + } + if (mode & AnimationFocus && !_focusData.contains(widget)) { + _focusData.insert(widget, new WidgetStateData(this, widget, duration()), enabled()); + } + if (mode & AnimationEnable && !_enableData.contains(widget)) { + _enableData.insert(widget, new EnableData(this, widget, duration()), enabled()); + } + if (mode & AnimationPressed && !_pressedData.contains(widget)) { + _pressedData.insert(widget, new WidgetStateData(this, widget, duration()), enabled()); + } + + // connect destruction signal + connect(widget, SIGNAL(destroyed(QObject *)), this, SLOT(unregisterWidget(QObject *)), Qt::UniqueConnection); + + return true; +} + +//____________________________________________________________ +BaseEngine::WidgetList WidgetStateEngine::registeredWidgets(AnimationModes mode) const +{ + WidgetList out; + + using Value = DataMap::Value; + + if (mode & AnimationHover) { + foreach (const Value &value, _hoverData) { + if (value) out.insert(value.data()->target().data()); + } + } + + if (mode & AnimationFocus) { + foreach (const Value &value, _focusData) { + if (value) out.insert(value.data()->target().data()); + } + } + + if (mode & AnimationEnable) { + foreach (const Value &value, _enableData) { + if (value) out.insert(value.data()->target().data()); + } + } + + if (mode & AnimationPressed) { + foreach (const Value &value, _pressedData) { + if (value) out.insert(value.data()->target().data()); + } + } + + return out; +} + +//____________________________________________________________ +bool WidgetStateEngine::updateState(const QObject *object, AnimationMode mode, bool value) +{ + DataMap::Value data(WidgetStateEngine::data(object, mode)); + return (data && data.data()->updateState(value)); +} + +//____________________________________________________________ +bool WidgetStateEngine::isAnimated(const QObject *object, AnimationMode mode) +{ + DataMap::Value data(WidgetStateEngine::data(object, mode)); + return (data && data.data()->animation() && data.data()->animation().data()->isRunning()); +} + +//____________________________________________________________ +DataMap::Value WidgetStateEngine::data(const QObject *object, AnimationMode mode) +{ + switch (mode) { + case AnimationHover: + return _hoverData.find(object).data(); + case AnimationFocus: + return _focusData.find(object).data(); + case AnimationEnable: + return _enableData.find(object).data(); + case AnimationPressed: + return _pressedData.find(object).data(); + default: + return DataMap::Value(); + } +} + +//____________________________________________________________ +DataMap &WidgetStateEngine::dataMap(AnimationMode mode) +{ + + switch (mode) { + default: + case AnimationHover: + return _hoverData; + case AnimationFocus: + return _focusData; + case AnimationEnable: + return _enableData; + case AnimationPressed: + return _pressedData; + } +} + +} // namespace Adwaita diff --git a/src/lib/animations/adwaitawidgetstateengine.h b/src/lib/animations/adwaitawidgetstateengine.h new file mode 100644 index 0000000..0387155 --- /dev/null +++ b/src/lib/animations/adwaitawidgetstateengine.h @@ -0,0 +1,191 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_WIDGET_STATE_ENGINE_H +#define ADWAITA_WIDGET_STATE_ENGINE_H + +#include "adwaita.h" +#include "adwaitabaseengine.h" +#include "adwaitadatamap.h" +#include "adwaitawidgetstatedata.h" +#include "adwaitaqt_export.h" + +namespace Adwaita +{ +//* used for simple widgets +class ADWAITAQT_EXPORT WidgetStateEngine : public BaseEngine +{ + Q_OBJECT +public: + //* constructor + explicit WidgetStateEngine(QObject *parent) + : BaseEngine(parent) + { + } + + //* destructor + virtual ~WidgetStateEngine() + { + } + + //* register widget + virtual bool registerWidget(QWidget *, AnimationModes); + + //* returns registered widgets + virtual WidgetList registeredWidgets(AnimationModes) const; + + using BaseEngine::registeredWidgets; + + //* true if widget hover state is changed + virtual bool updateState(const QObject *, AnimationMode, bool); + + //* true if widget is animated + virtual bool isAnimated(const QObject *, AnimationMode); + + //* animation opacity + virtual qreal opacity(const QObject *object, AnimationMode mode) + { + return isAnimated(object, mode) ? data(object, mode).data()->opacity() : AnimationData::OpacityInvalid; + } + + //* animation mode + /** precedence on focus */ + virtual AnimationMode frameAnimationMode(const QObject *object) + { + if (isAnimated(object, AnimationEnable)) { + return AnimationEnable; + } else if (isAnimated(object, AnimationFocus)) { + return AnimationFocus; + } else if (isAnimated(object, AnimationHover)) { + return AnimationHover; + } else { + return AnimationNone; + } + } + + //* animation opacity + /** precedence on focus */ + virtual qreal frameOpacity(const QObject *object) + { + if (isAnimated(object, AnimationEnable)) { + return data(object, AnimationEnable).data()->opacity(); + } else if (isAnimated(object, AnimationFocus)) { + return data(object, AnimationFocus).data()->opacity(); + } else if (isAnimated(object, AnimationHover)) { + return data(object, AnimationHover).data()->opacity(); + } else { + return AnimationData::OpacityInvalid; + } + } + + //* animation mode + /** precedence on mouseOver */ + virtual AnimationMode buttonAnimationMode(const QObject *object) + { + if (isAnimated(object, AnimationEnable)) { + return AnimationEnable; + } else if (isAnimated(object, AnimationPressed)) { + return AnimationPressed; + } else if (isAnimated(object, AnimationHover)) { + return AnimationHover; + } else if (isAnimated(object, AnimationFocus)) { + return AnimationFocus; + } else { + return AnimationNone; + } + } + + //* animation opacity + /** precedence on mouseOver */ + virtual qreal buttonOpacity(const QObject *object) + { + if (isAnimated(object, AnimationEnable)) { + return data(object, AnimationEnable).data()->opacity(); + } else if (isAnimated(object, AnimationPressed)) { + return data(object, AnimationPressed).data()->opacity(); + } else if (isAnimated(object, AnimationHover)) { + return data(object, AnimationHover).data()->opacity(); + } else if (isAnimated(object, AnimationFocus)) { + return data(object, AnimationFocus).data()->opacity(); + } else { + return AnimationData::OpacityInvalid; + } + } + + //* duration + virtual void setEnabled(bool value) + { + BaseEngine::setEnabled(value); + _hoverData.setEnabled(value); + _focusData.setEnabled(value); + _enableData.setEnabled(value); + _pressedData.setEnabled(value); + } + + //* duration + virtual void setDuration(int value) + { + BaseEngine::setDuration(value); + _hoverData.setDuration(value); + _focusData.setDuration(value); + _enableData.setDuration(value); + _pressedData.setDuration(value / 2); + } + +public Q_SLOTS: + //* remove widget from map + virtual bool unregisterWidget(QObject *object) + { + if (!object) { + return false; + } + bool found = false; + if (_hoverData.unregisterWidget(object)) { + found = true; + } + if (_focusData.unregisterWidget(object)) { + found = true; + } + if (_enableData.unregisterWidget(object)) { + found = true; + } + if (_pressedData.unregisterWidget(object)) { + found = true; + } + return found; + } + +protected: + //* returns data associated to widget + DataMap::Value data(const QObject *, AnimationMode); + + //* returns data map associated to animation mode + DataMap &dataMap(AnimationMode); + +private: + //* maps + DataMap _hoverData; + DataMap _focusData; + DataMap _enableData; + DataMap _pressedData; +}; + +} // namespace Adwaita + +#endif // ADWAITA_WIDGET_STATE_ENGINE_H diff --git a/src/lib/config-adwaita.h.cmake b/src/lib/config-adwaita.h.cmake new file mode 100644 index 0000000..c9bd0f8 --- /dev/null +++ b/src/lib/config-adwaita.h.cmake @@ -0,0 +1,28 @@ +/* config-adwaita.h. Generated by cmake from config-adwaita.h.cmake */ + +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef config_adwaita_h +#define config_adwaita_h + +/* Define to 1 if XCB libraries are found */ +#cmakedefine01 ADWAITA_HAVE_X11 + +#endif diff --git a/src/style/CMakeLists.txt b/src/style/CMakeLists.txt new file mode 100644 index 0000000..06ef956 --- /dev/null +++ b/src/style/CMakeLists.txt @@ -0,0 +1,44 @@ + +include_directories( + ${CMAKE_BINARY_DIR}/src/lib # for adwaita-config.h + ${CMAKE_SOURCE_DIR}/src/lib + ${CMAKE_CURRENT_SOURCE_DIR} +) + +set(Adwaita_SRCS + adwaitahelper.cpp + adwaitastyle.cpp + adwaitastyleplugin.cpp +) + +add_definitions(-DQT_PLUGIN) + +if (MSVC) + add_definitions(-D_USE_MATH_DEFINES) # Needed for M_PI on MSVC +endif() + +set(LIBRARY_NAME "adwaita-qt") + +add_library(${LIBRARY_NAME} MODULE ${Adwaita_SRCS}) +target_link_libraries(${LIBRARY_NAME} + adwaitaqt + adwaitaqtpriv + Qt5::Core + Qt5::DBus + Qt5::Gui + Qt5::Widgets +) + +if (ADWAITA_HAVE_X11) + target_link_libraries(${LIBRARY_NAME} ${XCB_LIBRARIES} Qt5::X11Extras) +endif() + +if (NOT APPLE AND NOT WIN32) + set_target_properties(${LIBRARY_NAME} PROPERTIES + LINK_FLAGS "-Wl,--no-undefined" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + OUTPUT_NAME "adwaita" + PREFIX "") +endif() + +install(TARGETS ${LIBRARY_NAME} DESTINATION "${QT_PLUGINS_DIR}/styles") diff --git a/src/style/adwaita.json b/src/style/adwaita.json new file mode 100644 index 0000000..64fc989 --- /dev/null +++ b/src/style/adwaita.json @@ -0,0 +1 @@ +{ "Keys": [ "Adwaita", "Adwaita-Dark" ] } diff --git a/src/style/adwaita.themerc b/src/style/adwaita.themerc new file mode 100644 index 0000000..2be74ad --- /dev/null +++ b/src/style/adwaita.themerc @@ -0,0 +1,5 @@ +[Misc] +Name=Adwaita +Comment=Qt widget style for GNOME +[KDE] +WidgetStyle=Adwaita diff --git a/src/style/adwaitahelper.cpp b/src/style/adwaitahelper.cpp new file mode 100644 index 0000000..1fb846b --- /dev/null +++ b/src/style/adwaitahelper.cpp @@ -0,0 +1,1418 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * Copyright (C) 2014-2018 Martin Bříza * + * Copyright (C) 2019-2020 Jan Grulich * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaita.h" +#include "adwaitacolors.h" +#include "adwaitahelper.h" + +#include +#include +#include + +#if ADWAITA_HAVE_X11 +#include +#endif + +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + +namespace Adwaita +{ + +//* contrast for arrow and treeline rendering +static const qreal arrowShade = 0.15; + +//____________________________________________________________________ +Helper::Helper() +{ + init(); +} + +// static +bool Helper::isWindowActive(const QWidget *widget) +{ + const QWindow *win = widget ? widget->window()->windowHandle() : nullptr; + if (win) { + return win->isActive(); + } + return false; +} + +//______________________________________________________________________________ +void Helper::renderDebugFrame(QPainter *painter, const QRect &rect) const +{ + painter->save(); + painter->setRenderHints(QPainter::Antialiasing); + painter->setBrush(Qt::NoBrush); + painter->setPen(Qt::red); + painter->drawRect(QRectF(rect).adjusted(0.5, 0.5, -0.5, -0.5)); + painter->restore(); +} + +//______________________________________________________________________________ +void Helper::renderFocusRect(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, Sides sides) const +{ + if (!color.isValid()) + return; + + painter->save(); + painter->setRenderHints(QPainter::Antialiasing); + painter->setBrush(color); + + if (!(outline.isValid() && sides)) { + painter->setPen(Qt::NoPen); + painter->drawRect(rect); + } else { + painter->setClipRect(rect); + + QRectF copy(rect); + copy.adjust(0.5, 0.5, -0.5, -0.5); + + qreal radius(frameRadius(-1.0)); + if (!(sides & SideTop)) + copy.adjust(0, -radius, 0, 0); + if (!(sides & SideBottom)) + copy.adjust(0, 0, 0, radius); + if (!(sides & SideLeft)) + copy.adjust(-radius, 0, 0, 0); + if (!(sides & SideRight)) + copy.adjust(0, 0, radius, 0); + + painter->setPen(outline); + // painter->setBrush( Qt::NoBrush ); + painter->drawRoundedRect(copy, radius, radius); + } + + painter->restore(); + return; +} + +//______________________________________________________________________________ +void Helper::renderFocusLine(QPainter *painter, const QRect &rect, const QColor &color) const +{ + if (!color.isValid()) + return; + + painter->save(); + + QPen pen(color, 1); + pen.setStyle(Qt::DotLine); + + painter->setRenderHint(QPainter::Antialiasing, false); + painter->setPen(pen); + painter->setBrush(Qt::NoBrush); + + painter->drawRoundedRect(rect, 1, 1); + + painter->restore(); +} + +//______________________________________________________________________________ +void Helper::renderFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, bool hasFocus) const +{ + painter->setRenderHint(QPainter::Antialiasing); + + QRectF frameRect(rect.adjusted(1, 1, -1, -1)); + qreal radius(frameRadius()); + + // set pen + if (outline.isValid()) { + if (hasFocus) { + painter->setPen(QPen(outline, 2)); + frameRect.adjust(0.5, 0.5, -0.5, -0.5); + } else { + painter->setPen(outline); + } + frameRect.adjust(0.5, 0.5, -0.5, -0.5); + radius = qMax(radius - 1, qreal(0.0)); + + } else { + painter->setPen(Qt::NoPen); + } + + // set brush + if (color.isValid()) + painter->setBrush(color); + else + painter->setBrush(Qt::NoBrush); + + // render + painter->drawRoundedRect(frameRect, radius, radius); +} + +//______________________________________________________________________________ +void Helper::renderSquareFrame(QPainter *painter, const QRect &rect, QColor color, bool hasFocus) const +{ + painter->setPen(color); + painter->drawRect(rect.adjusted(1, 1, -2, -2)); + if (hasFocus) { + color.setAlphaF(0.5); + painter->setPen(color); + painter->drawRect(rect.adjusted(0, 0, -1, -1)); + } +} + +//______________________________________________________________________________ +void Helper::renderFlatFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, bool hasFocus) const +{ + painter->setRenderHint(QPainter::Antialiasing); + + QRectF frameRect(rect.adjusted(1, 1, -1, -1)); + qreal radius(frameRadius()); + + // set pen + if (outline.isValid()) { + if (hasFocus) { + painter->setPen(QPen(outline, 2)); + frameRect.adjust(0.5, 0.5, -0.5, -0.5); + } else { + painter->setPen(outline); + } + frameRect.adjust(0.5, 0.5, -0.5, -0.5); + radius = qMax(radius - 1, qreal(0.0)); + } else { + painter->setPen(Qt::NoPen); + } + + // set brush + if (color.isValid()) + painter->setBrush(color); + else + painter->setBrush(Qt::NoBrush); + + QPainterPath path; + path.setFillRule(Qt::WindingFill); + path.addRect(frameRect.adjusted(2 * radius, 0, 0, 0)); + path.addRoundedRect(frameRect.adjusted(0, 0, - 2 * radius, 0), radius, radius); + + painter->drawPath(path.simplified()); + + // render + //painter->drawRoundedRect( frameRect, radius, radius ); +} + +//______________________________________________________________________________ +void Helper::renderSidePanelFrame(QPainter *painter, const QRect &rect, const QColor &outline, Side side) const +{ + // check color + if (!outline.isValid()) + return; + + // adjust rect + QRectF frameRect(rect.adjusted(1, 1, -1, -1)); + frameRect.adjust(0.5, 0.5, -0.5, -0.5); + + // setup painter + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(outline); + + // render + switch (side) { + case SideLeft: + frameRect.adjust(0, 1, 0, -1); + painter->drawLine(frameRect.topRight(), frameRect.bottomRight()); + break; + case SideTop: + frameRect.adjust(1, 0, -1, 0); + painter->drawLine(frameRect.topLeft(), frameRect.topRight()); + break; + case SideRight: + frameRect.adjust(0, 1, 0, -1); + painter->drawLine(frameRect.topLeft(), frameRect.bottomLeft()); + break; + case SideBottom: + frameRect.adjust(1, 0, -1, 0); + painter->drawLine(frameRect.bottomLeft(), frameRect.bottomRight()); + break; + case AllSides: { + qreal radius(frameRadius(-1.0)); + painter->drawRoundedRect(frameRect, radius, radius); + break; + } + default: + break; + } +} + +//______________________________________________________________________________ +void Helper::renderMenuFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, bool roundCorners) const +{ + // set brush + if (color.isValid()) + painter->setBrush(color); + else + painter->setBrush(Qt::NoBrush); + + painter->setRenderHint(QPainter::Antialiasing, false); + QRectF frameRect(rect); + if (outline.isValid()) { + painter->setPen(outline); + frameRect.adjust(0.5, 0.5, -0.5, -0.5); + } else + painter->setPen(Qt::NoPen); + + painter->drawRect(frameRect); +} + +//______________________________________________________________________________ +void Helper::renderButtonFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, const QColor &shadow, + bool hasFocus, bool sunken, bool mouseOver, bool active, bool darkMode) const +{ + // setup painter + painter->setRenderHint(QPainter::Antialiasing, true); + + // copy rect + QRectF frameRect(rect); + frameRect.adjust(1, 1, -1, -1); + qreal radius(frameRadius()); + + if (outline.isValid()) { + painter->setPen(QPen(outline, 1.0)); + + frameRect.adjust(0.5, 0.5, -0.5, -0.5); + radius = qMax(radius - 1, qreal(0.0)); + } else + painter->setPen(Qt::NoPen); + + // content + if (color.isValid() && active) { + QLinearGradient gradient(frameRect.bottomLeft(), frameRect.topLeft()); + if (sunken) { + // Pressed button in normal and dark mode is not a gradient, just an image consting from same $color + gradient.setColorAt(0, color); + gradient.setColorAt(1, color); + } else if (mouseOver) { + if (darkMode) { + QColor baseColor = Colors::lighten(color, 0.01); + // Hovered button in dark mode is a gradient from $color to Colors::lighten(bg_color, 0.01) + gradient.setColorAt(0, Colors::lighten(baseColor, 0.01)); // FIXME not correct according to adwaita's _drawing.scss file, but looks more close than before + gradient.setColorAt(1, Colors::lighten(baseColor, 0.01)); + } else { + QColor baseColor = color; + // Hovered button in normal mode is a gradient from $color to Colors::lighten(bg_color, 0.01) + gradient.setColorAt(0, color); + gradient.setColorAt(1, Colors::lighten(baseColor, 0.01)); + } + } else { + if (darkMode) { + QColor baseColor = Colors::lighten(color, 0.01); + // Normal button in dark mode is a gradient from $color to bg_color + gradient.setColorAt(0, color); + gradient.setColorAt(1, baseColor); + } else { + QColor baseColor = Colors::lighten(color, 0.04); + // Normal button in normal mode is a gradient from $color to bg_color + gradient.setColorAt(0, color); + gradient.setColorAt(1, baseColor); + } + } + painter->setBrush(gradient); + } else if (!active) { + painter->setBrush(color); + } else { + painter->setBrush(Qt::NoBrush); + } + + // render + painter->drawRoundedRect(frameRect, radius, radius); + + if (!sunken && active && color.isValid()) { + painter->setPen(color.lighter(140)); + painter->drawLine(frameRect.topLeft() + QPoint(3, 1), frameRect.topRight() + QPoint(-3, 1)); + painter->setPen(outline.darker(114)); + painter->drawLine(frameRect.bottomLeft() + QPointF(2.7, 0), frameRect.bottomRight() + QPointF(-2.7, 0)); + } +} + +//______________________________________________________________________________ +void Helper::renderCheckBoxFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, const QColor &shadow, + bool hasFocus, bool sunken, bool mouseOver, bool active, CheckBoxState state, bool darkMode, bool inMenu) const +{ + // setup painter + painter->setRenderHint(QPainter::Antialiasing, true); + + // copy rect + QRectF frameRect(rect); + frameRect.adjust(1, 1, -1, -1); + qreal radius(frameRadius()); + + if (outline.isValid()) { + painter->setPen(QPen(outline, 1.0)); + + frameRect.adjust(0.5, 0.5, -0.5, -0.5); + radius = qMax(radius - 1, qreal(0.0)); + } else + painter->setPen(Qt::NoPen); + + if (inMenu || state == CheckOff) { + // content + if (color.isValid() && active) { + QLinearGradient gradient(frameRect.bottomLeft(), frameRect.topLeft()); + if (sunken) { + // Pressed-alt button in dark mode is not a gradient, just an image consting from same $color + if (darkMode) { + gradient.setColorAt(0, color); + gradient.setColorAt(1, color); + } else { + // Pressed-alt button in normal mode is not a gradient, just an image consting from same $color + gradient.setColorAt(0, color); + gradient.setColorAt(1, color); + } + } else if (mouseOver) { + if (darkMode) { + QColor baseColor = color; + // Hovered-alt button in dark mode is a gradient from $color to Colors::darken(bg_color, 0.04) + gradient.setColorAt(0, Colors::darken(baseColor, 0.04)); + gradient.setColorAt(1, color); + } else { + QColor baseColor = Colors::darken(color, 0.09); + // Hovered-alt button in normal mode is a gradient from $color to Colors::lighten(bg_color, 0.04) + gradient.setColorAt(0, color); // FIXME: + gradient.setColorAt(1, Colors::lighten(baseColor, 0.04)); // should be vice-versa, but this way it seems to be more accurate + } + } else { + if (darkMode) { + QColor baseColor = Colors::lighten(color, 0.03); + // Normal-alt button in dark mode is a gradient from $color to Colors::darken(bg_color, 0.06) + gradient.setColorAt(0, Colors::darken(baseColor, 0.06)); + gradient.setColorAt(1, color); + } else { + QColor baseColor = Colors::darken(color, 0.05); + // Normal-alt button in normal mode is a gradient from $color to bg_color + gradient.setColorAt(0, baseColor); + gradient.setColorAt(1, color); + } + } + painter->setBrush(gradient); + } else if (!active) { + painter->setBrush(color); + } else { + painter->setBrush(Qt::NoBrush); + } + } else { + if (color.isValid()) { + QLinearGradient gradient(frameRect.bottomLeft(), frameRect.topLeft()); + gradient.setColorAt(0, color); + gradient.setColorAt(1, Colors::lighten(color, 0.04)); + painter->setBrush(gradient); + } else { + painter->setBrush(Qt::NoBrush); + } + } + + // render + painter->drawRoundedRect(frameRect, radius, radius); +} + +//______________________________________________________________________________ +void Helper::renderFlatButtonFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, const QColor &shadow, + bool hasFocus, bool sunken, bool mouseOver, bool active) const +{ + // setup painter + painter->setRenderHint(QPainter::Antialiasing, true); + + // copy rect + QRectF frameRect(rect); + frameRect.adjust(1, 1, -1, -1); + qreal radius(frameRadius()); + + if (outline.isValid()) { + painter->setPen(QPen(outline, 1.0)); + + frameRect.adjust(0.5, 0.5, -0.5, -0.5); + radius = qMax(radius - 1, qreal(0.0)); + } else + painter->setPen(Qt::NoPen); + + // content + if (color.isValid()) { + QLinearGradient gradient(frameRect.topLeft(), frameRect.bottomLeft()); + //gradient.setColorAt( 0, color.darker( sunken ? 110 : (hasFocus|mouseOver) ? 85 : 100 ) ); + //gradient.setColorAt( 1, color.darker( sunken ? 130 : (hasFocus|mouseOver) ? 95 : 110 ) ); + + if (!active) { + gradient.setColorAt(0, color); + } else if (sunken) { + gradient.setColorAt(0, color); + } else { + gradient.setColorAt(0, Colors::mix(color, Qt::white, 0.07)); + gradient.setColorAt(1, Colors::mix(color, Qt::black, 0.1)); + } + painter->setBrush(gradient); + } else + painter->setBrush(Qt::NoBrush); + + QPainterPath path; + path.setFillRule(Qt::WindingFill); + path.addRoundedRect(frameRect.adjusted(2 * radius, 0, 0, 0), radius, radius); + path.addRect(frameRect.adjusted(0, 0, -2 * radius, 0)); + painter->drawPath(path.simplified()); + + if (!sunken && active) { + painter->setPen(color.lighter(140)); + painter->drawLine(frameRect.topLeft() + QPoint(1, 1), frameRect.topRight() + QPoint(-3, 1)); + painter->setPen(outline.darker(114)); + painter->drawLine(frameRect.bottomLeft() + QPointF(0.7, 0), frameRect.bottomRight() + QPointF(-2.7, 0)); + } + + // render + //painter->drawRoundedRect( frameRect, radius, radius ); +} + +//______________________________________________________________________________ +void Helper::renderToolButtonFrame(QPainter *painter, const QRect &rect, const QColor &color, bool sunken) const +{ + // do nothing for invalid color + if (!color.isValid()) + return; + + // setup painter + painter->setRenderHints(QPainter::Antialiasing); + + QRectF baseRect(rect); + + if (sunken) { + qreal radius(frameRadius()); + + painter->setPen(Qt::NoPen); + painter->setBrush(color); + + QRectF contentRect(baseRect.adjusted(1, 1, -1, -1)); + painter->drawRoundedRect(contentRect, radius, radius); + } else { + qreal radius(frameRadius(-0.5)); + + painter->setPen(color); + painter->setBrush(Qt::NoBrush); + QRectF outlineRect(baseRect.adjusted(1.5, 1.5, -1.5, -1.5)); + painter->drawRoundedRect(outlineRect, radius, radius); + } +} + +//______________________________________________________________________________ +void Helper::renderToolBoxFrame(QPainter *painter, const QRect &rect, int tabWidth, const QColor &outline) const +{ + if (!outline.isValid()) + return; + + // round radius + qreal radius(frameRadius()); + QSizeF cornerSize(2 * radius, 2 * radius); + + // if rect - tabwidth is even, need to increase tabWidth by 1 unit + // for anti aliasing + if (!((rect.width() - tabWidth) % 2)) + ++tabWidth; + + // adjust rect for antialiasing + QRectF baseRect(rect); + baseRect.adjust(0.5, 0.5, -0.5, -0.5); + + // create path + QPainterPath path; + path.moveTo(0, baseRect.height() - 1); + path.lineTo((baseRect.width() - tabWidth) / 2 - radius, baseRect.height() - 1); + path.arcTo(QRectF(QPointF((baseRect.width() - tabWidth) / 2 - 2 * radius, baseRect.height() - 1 - 2 * radius), cornerSize), 270, 90); + path.lineTo((baseRect.width() - tabWidth) / 2, radius); + path.arcTo(QRectF(QPointF((baseRect.width() - tabWidth) / 2, 0), cornerSize), 180, -90); + path.lineTo((baseRect.width() + tabWidth) / 2 - 1 - radius, 0); + path.arcTo(QRectF(QPointF((baseRect.width() + tabWidth) / 2 - 1 - 2 * radius, 0), cornerSize), 90, -90); + path.lineTo((baseRect.width() + tabWidth) / 2 - 1, baseRect.height() - 1 - radius); + path.arcTo(QRectF(QPointF((baseRect.width() + tabWidth) / 2 - 1, baseRect.height() - 1 - 2 * radius), cornerSize), 180, 90); + path.lineTo(baseRect.width() - 1, baseRect.height() - 1); + + // render + painter->save(); + painter->setRenderHints(QPainter::Antialiasing); + painter->setBrush(Qt::NoBrush); + painter->setPen(outline); + painter->translate(baseRect.topLeft()); + painter->drawPath(path); + painter->restore(); + + return; +} + +//______________________________________________________________________________ +void Helper::renderTabWidgetFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, Corners corners) const +{ + painter->setRenderHint(QPainter::Antialiasing); + + QRectF frameRect(rect.adjusted(1, 1, -1, -1)); + qreal radius(frameRadius()); + + // set pen + if (outline.isValid()) { + painter->setPen(outline); + frameRect.adjust(0.5, 0.5, -0.5, -0.5); + radius = qMax(radius - 1, qreal(0.0)); + } else + painter->setPen(Qt::NoPen); + + // set brush + if (color.isValid()) + painter->setBrush(color); + else + painter->setBrush(Qt::NoBrush); + + // render + QPainterPath path(roundedPath(frameRect, corners, radius)); + painter->drawPath(path); +} + +//______________________________________________________________________________ +void Helper::renderSelection(QPainter *painter, const QRect &rect, const QColor &color) const +{ + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(Qt::NoPen); + painter->setBrush(color); + painter->drawRect(rect); +} + +//______________________________________________________________________________ +void Helper::renderSeparator(QPainter *painter, const QRect &rect, const QColor &color, bool vertical) const +{ + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, false); + painter->setBrush(Qt::NoBrush); + painter->setPen(color); + + if (vertical) { + painter->translate(rect.width() / 2, 0); + painter->drawLine(rect.topLeft(), rect.bottomLeft()); + } else { + painter->translate(0, rect.height() / 2); + painter->drawLine(rect.topLeft(), rect.topRight()); + } + + painter->restore(); + + return; +} + +//______________________________________________________________________________ +void Helper::renderCheckBoxBackground(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, bool sunken) const +{ + // setup painter + painter->setRenderHint(QPainter::Antialiasing, true); + + // copy rect and radius + QRectF frameRect(rect); + frameRect.adjust(3, 3, -3, -3); + + painter->setPen(outline); + painter->setBrush(color); + painter->drawRect(frameRect); +} + +//______________________________________________________________________________ +void Helper::renderCheckBox(QPainter *painter, const QRect &rect, const QColor &background, const QColor &outline, const QColor &tickColor, + bool sunken, CheckBoxState state, bool mouseOver, qreal animation, bool active, bool darkMode, bool inMenu) const +{ + // setup painter + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, true); + + // copy rect and radius + QRectF frameRect(rect); + frameRect.adjust(2, 2, -2, -2); + qreal radius(frameRadius()); + + // content + { + renderCheckBoxFrame(painter, rect, background, outline, Qt::transparent, false, sunken, mouseOver, active, state, darkMode, inMenu); + } + + // mark + if (state == CheckOn) { + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + painter->setBrush(Qt::NoBrush); + QPen pen(tickColor, 3); + pen.setJoinStyle(Qt::MiterJoin); + painter->setPen(pen); + + QRectF markerRect(frameRect); + + QPainterPath path; + path.moveTo(markerRect.right() - markerRect.width() / 4, markerRect.top() + markerRect.height() / 3); + path.lineTo(markerRect.center().x(), markerRect.bottom() - markerRect.height() / 3.0); + path.lineTo(markerRect.left() + markerRect.width() / 4, markerRect.center().y()); + + painter->setClipRect(markerRect); + painter->drawPath(path); + painter->restore(); + } else if (state == CheckPartial) { + QPen pen(tickColor, 4); + pen.setCapStyle(Qt::RoundCap); + painter->setPen(pen); + + QRectF markerRect(frameRect.adjusted(4, 4, -4, -4)); + + painter->drawLine(markerRect.center() - QPoint(3, 0), markerRect.center() + QPoint(3, 0)); + } else if (state == CheckAnimated) { + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + painter->setBrush(Qt::NoBrush); + QPen pen(tickColor, 3); + pen.setJoinStyle(Qt::MiterJoin); + painter->setPen(pen); + + QRectF markerRect(frameRect); + + QPainterPath path; + path.moveTo(markerRect.right() - markerRect.width() / 4, markerRect.top() + markerRect.height() / 3); + path.lineTo(markerRect.center().x(), markerRect.bottom() - markerRect.height() / 3.0); + path.lineTo(markerRect.left() + markerRect.width() / 4, markerRect.center().y()); + path.translate(-markerRect.right(), -markerRect.top()); + + painter->setClipRect(markerRect.adjusted(1, 1, -1, -1)); + painter->translate(markerRect.right(), markerRect.top()); + painter->scale(animation, 0.5 + 0.5 * animation); + painter->drawPath(path); + painter->restore(); + } + + painter->restore(); +} + +//______________________________________________________________________________ +void Helper::renderRadioButtonBackground(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, bool sunken) const +{ + // setup painter + painter->setRenderHint(QPainter::Antialiasing, true); + + // copy rect + QRectF frameRect(rect); + frameRect.adjust(3, 3, -3, -3); + if (sunken) + frameRect.translate(1, 1); + + painter->setPen(outline); + painter->setBrush(color); + painter->drawEllipse(frameRect); +} + +//______________________________________________________________________________ +void Helper::renderRadioButton(QPainter *painter, const QRect &rect, const QColor &background, const QColor &outline, const QColor &tickColor, + bool sunken, bool enabled, RadioButtonState state, qreal animation, bool mouseOver, bool darkMode, bool inMenu) const +{ + // setup painter + painter->setRenderHint(QPainter::Antialiasing, true); + + // copy rect + QRectF frameRect(rect); + frameRect.adjust(2, 2, -2, -2); + + if (inMenu || state == RadioOff) { + if (background.isValid() && enabled) { + QLinearGradient gradient(frameRect.bottomLeft(), frameRect.topLeft()); + if (sunken) { + // Pressed-alt button in normal and dark mode is not a gradient, just an image consting from same $background + gradient.setColorAt(0, background); + gradient.setColorAt(1, background); + } else if (mouseOver) { + if (darkMode) { + QColor baseColor = background; + // Hovered-alt button in dark mode is a gradient from $background to Colors::darken(bg_background, 0.04) + gradient.setColorAt(0, Colors::darken(baseColor, 0.04)); + gradient.setColorAt(1, background); + } else { + QColor baseColor = Colors::darken(background, 0.09); + // Hovered-alt button in normal mode is a gradient from $background to Colors::lighten(bg_background, 0.04) + gradient.setColorAt(0, background); // FIXME: + gradient.setColorAt(1, Colors::lighten(baseColor, 0.04)); // should be vice-versa, but this way it seems to be more accurate + } + } else { + if (darkMode) { + QColor baseColor = Colors::lighten(background, 0.03); + // Normal-alt button in dark mode is a gradient from $background to Colors::darken(bg_background, 0.06) + gradient.setColorAt(0, Colors::darken(baseColor, 0.06)); + gradient.setColorAt(1, background); + } else { + QColor baseColor = Colors::darken(background, 0.05); + // Normal-alt button in normal mode is a gradient from $background to bg_background + gradient.setColorAt(0, baseColor); + gradient.setColorAt(1, background); + } + } + painter->setBrush(gradient); + } else if (!enabled) { + painter->setBrush(background); + } else { + painter->setBrush(Qt::NoBrush); + } + + painter->setPen(QPen(outline, 1)); + + QRectF contentRect(frameRect.adjusted(0.5, 0.5, -0.5, -0.5)); + painter->drawEllipse(contentRect); + } else { + if (background.isValid()) { + QLinearGradient gradient(frameRect.bottomLeft(), frameRect.topLeft()); + gradient.setColorAt(0, background); + gradient.setColorAt(1, Colors::lighten(background, 0.04)); + painter->setBrush(gradient); + } else { + painter->setBrush(Qt::NoBrush); + } + + painter->setPen(QPen(outline, 1)); + + QRectF contentRect(frameRect.adjusted(0.5, 0.5, -0.5, -0.5)); + painter->drawEllipse(contentRect); + } + + // mark + if (state == RadioOn) { + painter->setBrush(tickColor); + painter->setPen(Qt::NoPen); + + QRectF markerRect(frameRect.adjusted(5, 5, -5, -5)); + painter->drawEllipse(markerRect); + } else if (state == RadioAnimated) { + painter->setBrush(tickColor); + painter->setPen(Qt::NoPen); + QRectF markerRect(frameRect.adjusted(5, 5, -5, -5)); + qreal remaining = markerRect.width() / 2.0 * (1.0 - animation); + markerRect.adjust(remaining, remaining, -remaining, -remaining); + + painter->drawEllipse(markerRect); + } +} + +//______________________________________________________________________________ +void Helper::renderSliderGroove(QPainter *painter, const QRect &rect, const QColor &color) const +{ + // setup painter + painter->setRenderHint(QPainter::Antialiasing, true); + + QRectF baseRect(rect); + qreal radius(0.5 * Metrics::Slider_GrooveThickness); + + // content + if (color.isValid()) { + painter->setPen(Qt::NoPen); + painter->setBrush(color); + painter->drawRoundedRect(baseRect, radius, radius); + } + + return; +} + +//______________________________________________________________________________ +void Helper::renderDialGroove(QPainter *painter, const QRect &rect, const QColor &color) const +{ + // setup painter + painter->setRenderHint(QPainter::Antialiasing, true); + + QRectF baseRect(rect); + + // content + if (color.isValid()) { + qreal penWidth(Metrics::Slider_GrooveThickness); + QRectF grooveRect(rect.adjusted(penWidth / 2, penWidth / 2, -penWidth / 2, -penWidth / 2)); + + painter->setPen(QPen(color, penWidth)); + painter->setBrush(Qt::NoBrush); + painter->drawEllipse(grooveRect); + } + + return; +} + +//______________________________________________________________________________ +void Helper::renderDialContents(QPainter *painter, const QRect &rect, const QColor &color, qreal first, qreal second) const +{ + // setup painter + painter->setRenderHint(QPainter::Antialiasing, true); + + QRectF baseRect(rect); + + // content + if (color.isValid()) { + // setup groove rect + qreal penWidth(Metrics::Slider_GrooveThickness); + QRectF grooveRect(rect.adjusted(penWidth / 2, penWidth / 2, -penWidth / 2, -penWidth / 2)); + + // setup angles + int angleStart(first * 180 * 16 / M_PI); + int angleSpan((second - first) * 180 * 16 / M_PI); + + // setup pen + if (angleSpan != 0) { + QPen pen(color, penWidth); + pen.setCapStyle(Qt::RoundCap); + painter->setPen(pen); + painter->setBrush(Qt::NoBrush); + painter->drawArc(grooveRect, angleStart, angleSpan); + } + } + + return; +} + +//______________________________________________________________________________ +void Helper::renderSliderHandle(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, const QColor &shadow, + bool sunken, bool enabled, Side ticks, qreal angle, bool darkMode) const +{ + // setup painter + painter->setRenderHint(QPainter::Antialiasing, true); + + // copy rect + QRectF frameRect(rect); + frameRect.adjust(1, 1, -1, -1); + + // set pen + if (outline.isValid()) { + QPen pen(outline); + pen.setCapStyle(Qt::FlatCap); + pen.setJoinStyle(Qt::MiterJoin); + painter->setPen(pen); + + frameRect.adjust(0.5, 0.5, -0.5, -0.5); + } else + painter->setPen(Qt::NoPen); + + // set brush + if (color.isValid() && enabled) { + QLinearGradient gradient(frameRect.bottomLeft(), frameRect.topLeft()); + if (sunken) { + // Pressed-alt button in normal and dark mode is not a gradient, just an image consting from same $background + gradient.setColorAt(0, color); + gradient.setColorAt(1, color); + } else { + if (darkMode) { + QColor baseColor = Colors::lighten(color, 0.03); + // Normal-alt button in dark mode is a gradient from $color to Colors::darken(bg_background, 0.06) + gradient.setColorAt(0, Colors::darken(baseColor, 0.06)); + gradient.setColorAt(1, color); + } else { + QColor baseColor = Colors::darken(color, 0.05); + // Normal-alt button in normal mode is a gradient from $color to bg_background + gradient.setColorAt(0, baseColor); + gradient.setColorAt(1, color); + } + } + painter->setBrush(gradient); + } else if (!enabled) { + painter->setBrush(color); + } else { + painter->setBrush(Qt::NoBrush); + } + + QRect r(rect.right() - rect.height(), rect.top(), rect.height(), rect.height()); + r.adjust(4.5, 3.5, -2.5, -3.5); + + QPainterPath circle; + circle.addEllipse(r); + circle.closeSubpath(); + + if (ticks & SideBottom) { + QPainterPath triangle(r.center()); + triangle.moveTo(r.left() + 1.5, r.center().y() + 5.5); + triangle.lineTo(r.center().x() + 1, r.bottom() + 4.5); + triangle.lineTo(r.right() - 0.5, r.center().y() + 5.5); + triangle.closeSubpath(); + circle = circle.united(triangle); + } else if (ticks & SideTop) { + QPainterPath triangle(r.center()); + triangle.moveTo(r.left() + 1.5, r.center().y() - 3.5); + triangle.lineTo(r.center().x() + 1, r.top() - 2.5); + triangle.lineTo(r.right() - 0.5, r.center().y() - 3.5); + triangle.closeSubpath(); + circle = circle.united(triangle); + } else if (ticks & SideLeft) { + QPainterPath triangle(r.center()); + triangle.moveTo(r.center().x() - 3.5, r.top() + 1.5); + triangle.lineTo(r.left() - 2.5, r.center().y() + 1); + triangle.lineTo(r.center().x() - 3.5, r.bottom() - 0.5); + triangle.closeSubpath(); + circle = circle.united(triangle); + } else if (ticks & SideRight) { + QPainterPath triangle(r.center()); + triangle.moveTo(r.center().x() + 3.5, r.top() + 1.5); + triangle.lineTo(r.right() + 2.5, r.center().y() + 1); + triangle.lineTo(r.center().x() + 3.5, r.bottom() - 0.5); + triangle.closeSubpath(); + circle = circle.united(triangle); + } + + QTransform rotate; + rotate.translate(frameRect.center().x(), frameRect.center().y()); + rotate.rotate(angle); + rotate.translate(-frameRect.center().x(), -frameRect.center().y()); + painter->drawPolygon(circle.toFillPolygon(rotate)); +} + +//______________________________________________________________________________ +void Helper::renderProgressBarGroove(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline) const +{ + // setup painter + painter->setRenderHint(QPainter::Antialiasing, true); + painter->setRenderHint(QPainter::SmoothPixmapTransform, true); + + QRectF baseRect(rect); + qreal radius(0.5); + + // content + if (color.isValid()) { + painter->setPen(outline); + painter->setBrush(color); + painter->drawRoundedRect(baseRect.translated(0.5, 0.5), radius, radius); + } + + return; +} + + +//______________________________________________________________________________ +void Helper::renderProgressBarBusyContents(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, + bool horizontal, bool reverse, int progress) const +{ + Q_UNUSED(reverse); + + // setup painter + painter->setRenderHint(QPainter::Antialiasing, true); + + QRectF baseRect(rect); + qreal radius(0.25 * Metrics::ProgressBar_Thickness); + QRectF contentRect; + if (horizontal) { + contentRect = QRect(baseRect.left(), baseRect.top(), Metrics::ProgressBar_BusyIndicatorSize, baseRect.height()); + contentRect.translate(fabs(progress - 50) / 50.0 * (baseRect.width() - contentRect.width()), 0); + } else { + contentRect = QRect(baseRect.left(), baseRect.top(), baseRect.width(), Metrics::ProgressBar_BusyIndicatorSize); + contentRect.translate(0, fabs(progress - 50) / 50.0 * (baseRect.height() - contentRect.height())); + } + + painter->setBrush(color); + painter->setPen(outline); + painter->drawRoundedRect(contentRect.translated(0.5, 0.5), radius, radius); + + return; +} + +//______________________________________________________________________________ +void Helper::renderScrollBarHandle(QPainter *painter, const QRect &rect, const QColor &color) const +{ + // setup painter + painter->setRenderHint(QPainter::Antialiasing, true); + + QRectF baseRect(rect); + qreal metric(rect.width() < rect.height() ? rect.width() : rect.height()); + qreal radius(0.5 * metric); + + // content + if (color.isValid()) { + painter->setPen(Qt::NoPen); + painter->setBrush(color); + painter->drawRoundedRect(baseRect, radius, radius); + } + + return; +} + +//______________________________________________________________________________ +void Helper::renderTabBarTab(QPainter *painter, const QRect &rect, const QColor &background, const QColor &color, const QColor &outline, Corners corners, bool renderFrame) const +{ + // setup painter + painter->setRenderHint(QPainter::Antialiasing, false); + + QRectF frameRect(rect); + qreal adjustment; + + // pen + if (outline.isValid()) { + painter->setPen(outline); + frameRect.adjust(1.0, 1.0, -1.0, -1.0); + adjustment = 0; + + painter->setBrush(background); + + // render + painter->drawRect(frameRect); + } else if (!renderFrame) { + adjustment = 9; + } + + painter->setPen(QPen(color, 6)); + + switch (corners) { + case CornersTop: + painter->drawLine(frameRect.left() + adjustment, frameRect.bottom(), frameRect.right() - adjustment, frameRect.bottom()); + break; + case CornersBottom: + painter->drawLine(frameRect.left() + adjustment, frameRect.top(), frameRect.right() - adjustment, frameRect.top()); + break; + case CornersLeft: + painter->drawLine(frameRect.right(), frameRect.top() + adjustment, frameRect.right(), frameRect.bottom() - adjustment); + break; + case CornersRight: + painter->drawLine(frameRect.left(), frameRect.top() + adjustment, frameRect.left(), frameRect.bottom() - adjustment); + break; + } +} + +//______________________________________________________________________________ +// TODO blurry edges +void Helper::renderArrow(QPainter *painter, const QRect &rect, const QColor &color, ArrowOrientation orientation) const +{ + // define polygon + QPolygonF arrow; + switch (orientation) { + case ArrowUp: + arrow << QPointF(-4, 2) << QPointF(0, -2) << QPointF(4, 2); + break; + case ArrowDown: + arrow << QPointF(-4, -2) << QPointF(0, 2) << QPointF(4, -2); + break; + case ArrowLeft: + arrow << QPointF(2, -4) << QPointF(-2, 0) << QPointF(2, 4); + break; + case ArrowRight: + arrow << QPointF(-2, -4) << QPointF(2, 0) << QPointF(-2, 4); + break; + default: + break; + } + + QPen pen(color, 1.2); + pen.setCapStyle(Qt::FlatCap); + pen.setJoinStyle(Qt::MiterJoin); + + painter->save(); + painter->setRenderHints(QPainter::Antialiasing); + painter->translate(QRectF(rect).center()); + painter->setBrush(color); + painter->setPen(pen); + painter->drawPolygon(arrow); + + painter->restore(); + + return; +} + +void Helper::renderSign(QPainter *painter, const QRect &rect, const QColor &color, bool orientation) const +{ + QPen pen(color, 2); + pen.setCapStyle(Qt::FlatCap); + + QRect r = rect.adjusted(1, 2, 0, 0); + + painter->setPen(pen); + painter->drawLine(r.center() - QPointF(5, 0), r.center() + QPointF(5, 0)); + if (orientation) + painter->drawLine(r.center() - QPointF(0, 5), r.center() + QPointF(0, 5)); +} + +//______________________________________________________________________________ +void Helper::renderDecorationButton(QPainter *painter, const QRect &rect, const QColor &color, ButtonType buttonType, bool inverted) const +{ + painter->save(); + painter->setViewport(rect); + painter->setWindow(0, 0, 18, 18); + painter->setRenderHints(QPainter::Antialiasing, false); + + // initialize pen + QPen pen; + pen.setCapStyle(Qt::RoundCap); + pen.setJoinStyle(Qt::MiterJoin); + + painter->setBrush(Qt::NoBrush); + + pen.setColor(color); + pen.setCapStyle(Qt::RoundCap); + pen.setJoinStyle(Qt::MiterJoin); + pen.setWidthF(2.0 * qMax(1.0, 18.0 / rect.width())); + painter->setPen(pen); + + switch (buttonType) { + case ButtonClose: { + painter->setRenderHints(QPainter::Antialiasing, true); + painter->drawLine(QPointF(5, 5), QPointF(13, 13)); + painter->drawLine(13, 5, 5, 13); + break; + } + case ButtonMaximize: { + painter->drawPolyline(QPolygonF() + << QPointF(4, 4) + << QPointF(4, 14) + << QPointF(14, 14) + << QPointF(14, 4)); + break; + } + case ButtonMinimize: { + + painter->drawPolyline(QPolygonF() + << QPointF(4, 14) + << QPointF(14, 14)); + break; + } + case ButtonRestore: { + painter->setPen(pen); + QPolygonF rect = QPolygonF() << QPointF(0, 0) << QPointF(8, 0) << QPointF(8, 8) << QPointF(0, 8); + painter->drawPolygon(rect.translated(7, 3)); + painter->drawPolygon(rect.translated(3, 7)); + break; + } + default: + break; + } + + painter->restore(); + return; +} + +//______________________________________________________________________________ +bool Helper::isX11(void) +{ + static const bool s_isX11 = qApp->platformName() == QLatin1String("xcb"); + return s_isX11; +} + +bool Helper::isWayland(void) +{ + static const bool s_isWayland = qApp->platformName().startsWith(QLatin1String("wayland")); + return s_isWayland; +} + +//______________________________________________________________________________ +QRectF Helper::shadowRect(const QRectF &rect) const +{ + return rect; +} + +//______________________________________________________________________________ +QPainterPath Helper::roundedPath(const QRectF &rect, Corners corners, qreal radius) const +{ + QPainterPath path; + + // simple cases + if (corners == 0) { + path.addRect(rect); + return path; + } + + if (corners == AllCorners) { + path.addRoundedRect(rect, radius, radius); + return path; + } + + QSizeF cornerSize(2 * radius, 2 * radius); + + // rotate counterclockwise + // top left corner + if (corners & CornerTopLeft) { + path.moveTo(rect.topLeft() + QPointF(radius, 0)); + path.arcTo(QRectF(rect.topLeft(), cornerSize), 90, 90); + } else + path.moveTo(rect.topLeft()); + + // bottom left corner + if (corners & CornerBottomLeft) { + path.lineTo(rect.bottomLeft() - QPointF(0, radius)); + path.arcTo(QRectF(rect.bottomLeft() - QPointF(0, 2 * radius), cornerSize), 180, 90); + } else + path.lineTo(rect.bottomLeft()); + + // bottom right corner + if (corners & CornerBottomRight) { + path.lineTo(rect.bottomRight() - QPointF(radius, 0)); + path.arcTo(QRectF(rect.bottomRight() - QPointF(2 * radius, 2 * radius), cornerSize), 270, 90); + } else + path.lineTo(rect.bottomRight()); + + // top right corner + if (corners & CornerTopRight) { + path.lineTo(rect.topRight() + QPointF(0, radius)); + path.arcTo(QRectF(rect.topRight() - QPointF(2 * radius, 0), cornerSize), 0, 90); + } else + path.lineTo(rect.topRight()); + path.closeSubpath(); + return path; +} + +//________________________________________________________________________________________________________ +bool Helper::compositingActive(void) const +{ +#if ADWAITA_HAVE_X11 + if (isX11()) { + // direct call to X + xcb_get_selection_owner_cookie_t cookie(xcb_get_selection_owner(connection(), _compositingManagerAtom)); + ScopedPointer reply(xcb_get_selection_owner_reply(connection(), cookie, nullptr)); + return reply && reply->owner; + + } +#endif + + // use KWindowSystem + //return KWindowSystem::compositingActive(); + return false; + +} + +//____________________________________________________________________ +bool Helper::hasAlphaChannel(const QWidget *widget) const +{ + return compositingActive() && widget && widget->testAttribute(Qt::WA_TranslucentBackground); +} + +//______________________________________________________________________________________ +QPixmap Helper::highDpiPixmap(int width, int height) const +{ + qreal dpiRatio(qApp->devicePixelRatio()); + QPixmap pixmap(width * dpiRatio, height * dpiRatio); + pixmap.setDevicePixelRatio(dpiRatio); + return pixmap; +} + +//______________________________________________________________________________________ +qreal Helper::devicePixelRatio(const QPixmap &pixmap) const +{ + Q_UNUSED(pixmap); + return 1; +} + +#if ADWAITA_HAVE_X11 + +//____________________________________________________________________ +xcb_connection_t *Helper::connection(void) +{ + + return QX11Info::connection(); +} + +//____________________________________________________________________ +xcb_atom_t Helper::createAtom(const QString &name) const +{ + if (isX11()) { + xcb_connection_t *connection(Helper::connection()); + xcb_intern_atom_cookie_t cookie(xcb_intern_atom(connection, false, name.size(), qPrintable(name))); + ScopedPointer reply(xcb_intern_atom_reply(connection, cookie, nullptr)); + return reply ? reply->atom : 0; + } else + return 0; +} + +#endif + +//____________________________________________________________________ +void Helper::init(void) +{ +#if ADWAITA_HAVE_X11 + if (isX11()) { + // create compositing screen + QString atomName(QStringLiteral("_NET_WM_CM_S%1").arg(QX11Info::appScreen())); + _compositingManagerAtom = createAtom(atomName); + } +#endif +} + +//____________________________________________________________________ +void Helper::setVariant(QWidget *widget, const QByteArray &variant) +{ +#if ADWAITA_HAVE_X11 + if (isX11() && widget) { // && !widget->testAttribute(Qt::WA_)) { + static const char *_GTK_THEME_VARIANT = "_GTK_THEME_VARIANT"; + + // Check if already set + QVariant var = widget->property("_GTK_THEME_VARIANT"); + if (var.isValid() && var.toByteArray() == variant) { + return; + } + + // Typedef's from xcb/xcb.h - copied so that there is no + // direct xcb dependency + typedef quint32 XcbAtom; + + struct XcbInternAtomCookie { + unsigned int sequence; + }; + + struct XcbInternAtomReply { + quint8 response_type; + quint8 pad0; + quint16 sequence; + quint32 length; + XcbAtom atom; + }; + + struct XcbVoidCookie { + unsigned int sequence; + }; + + typedef void *(*XcbConnectFn)(const char *, int *); + typedef XcbInternAtomCookie(*XcbInternAtomFn)(void *, quint8, quint16, const char *); + typedef XcbInternAtomReply * (*XcbInternAtomReplyFn)(void *, XcbInternAtomCookie, void *); + typedef XcbVoidCookie(*XcbChangePropertyFn)(void *, quint8, quint32, XcbAtom, XcbAtom, quint8, quint32, const void *); + typedef int (*XcbFlushFn)(void *); + + static QLibrary *lib = 0; + static XcbAtom variantAtom = 0; + static XcbAtom utf8TypeAtom = 0; + static void *xcbConn = 0; + static XcbChangePropertyFn XcbChangePropertyFnPtr = 0; + static XcbFlushFn XcbFlushFnPtr = 0; + + if (!lib) { + lib = new QLibrary("libxcb", qApp); + + if (lib->load()) { + XcbConnectFn XcbConnectFnPtr = (XcbConnectFn)lib->resolve("xcb_connect"); + XcbInternAtomFn XcbInternAtomFnPtr = (XcbInternAtomFn)lib->resolve("xcb_intern_atom"); + XcbInternAtomReplyFn XcbInternAtomReplyFnPtr = (XcbInternAtomReplyFn)lib->resolve("xcb_intern_atom_reply"); + + XcbChangePropertyFnPtr = (XcbChangePropertyFn)lib->resolve("xcb_change_property"); + XcbFlushFnPtr = (XcbFlushFn)lib->resolve("xcb_flush"); + if (XcbConnectFnPtr && XcbInternAtomFnPtr && XcbInternAtomReplyFnPtr && XcbChangePropertyFnPtr && XcbFlushFnPtr) { + xcbConn = (*XcbConnectFnPtr)(0, 0); + if (xcbConn) { + XcbInternAtomReply *typeReply = (*XcbInternAtomReplyFnPtr)(xcbConn, (*XcbInternAtomFnPtr)(xcbConn, 0, 11, "UTF8_STRING"), 0); + + if (typeReply) { + XcbInternAtomReply *gtkVarReply = (*XcbInternAtomReplyFnPtr)(xcbConn, + (*XcbInternAtomFnPtr)(xcbConn, 0, strlen(_GTK_THEME_VARIANT), + _GTK_THEME_VARIANT), 0); + if (gtkVarReply) { + utf8TypeAtom = typeReply->atom; + variantAtom = gtkVarReply->atom; + free(gtkVarReply); + } + free(typeReply); + } + } + } + } + } + + if (0 != variantAtom) { + (*XcbChangePropertyFnPtr)(xcbConn, 0, widget->effectiveWinId(), variantAtom, utf8TypeAtom, 8, + variant.length(), (const void *)variant.constData()); + (*XcbFlushFnPtr)(xcbConn); + widget->setProperty(_GTK_THEME_VARIANT, variant); + } + } +#endif +} + +} diff --git a/src/style/adwaitahelper.h b/src/style/adwaitahelper.h new file mode 100644 index 0000000..f01440d --- /dev/null +++ b/src/style/adwaitahelper.h @@ -0,0 +1,292 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * Copyright (C) 2014-2018 Martin Bříza * + * Copyright (C) 2019-2020 Jan Grulich * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_HELPER_H +#define ADWAITA_HELPER_H + +#include "adwaita.h" +#include "animations/adwaitaanimationdata.h" +#include "config-adwaita.h" + +#include +#include +#include + +#include + +#if ADWAITA_HAVE_X11 +#include +#include +#endif + +#include + +namespace Adwaita +{ +//* adwaita style helper class. +/** contains utility functions used at multiple places in both adwaita style and adwaita window decoration */ +class Helper +{ +public: + //* constructor + explicit Helper(); + + //* destructor + virtual ~Helper() + { + } + + static bool isWindowActive(const QWidget *widget); + + //*@name rendering utilities + //@{ + + //* debug frame + void renderDebugFrame(QPainter *painter, const QRect &) const; + + //* focus rect + void renderFocusRect(QPainter *painter, const QRect &rect, const QColor &, const QColor &outline = QColor(), Sides = 0) const; + + //* focus line + void renderFocusLine(QPainter *painter, const QRect &rect, const QColor &) const; + + //* generic frame + void renderFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline = QColor(), bool hasFocus = false) const; + + //* square frame + void renderSquareFrame(QPainter *painter, const QRect &rect, QColor color, bool hasFocus) const; + + //* generic frame flat on right side + void renderFlatFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline = QColor(), bool hasFocus = false) const; + + //* side panel frame + void renderSidePanelFrame(QPainter *painter, const QRect &rect, const QColor &outline, Side) const; + + //* menu frame + void renderMenuFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, bool roundCorners = true) const; + + //* button frame + void renderButtonFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, const QColor &shadow, bool focus, bool sunken, bool mouseOver, bool active, bool darkMode = false) const; + + //* checkbox frame + void renderCheckBoxFrame(QPainter *painter, + const QRect &rect, + const QColor &color, + const QColor &outline, + const QColor &shadow, + bool focus, + bool sunken, + bool mouseOver, + bool active, + CheckBoxState state = CheckOff, + bool darkMode = false, + bool inMenu = false) const; + + //* button frame + void renderFlatButtonFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, const QColor &shadow, bool focus, bool sunken, bool mouseOver, bool active) const; + + //* toolbutton frame + void renderToolButtonFrame(QPainter *painter, const QRect &rect, const QColor &color, bool sunken) const; + + //* toolbutton frame + void renderToolBoxFrame(QPainter *painter, const QRect &rect, int tabWidth, const QColor &color) const; + + //* tab widget frame + void renderTabWidgetFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, Corners) const; + + //* selection frame + void renderSelection(QPainter *painter, const QRect &rect, const QColor &) const; + + //* separator + void renderSeparator(QPainter *painter, const QRect &rect, const QColor &, bool vertical = false) const; + + //* checkbox + void renderCheckBoxBackground(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, bool sunken) const; + + //* checkbox + void renderCheckBox(QPainter *painter, + const QRect &rect, + const QColor &background, + const QColor &outline, + const QColor &tickColor, + bool sunken, + CheckBoxState state, + bool mouseOver, + qreal animation = AnimationData::OpacityInvalid, + bool active = true, + bool darkMode = false, + bool inMenu = false) const; + + //* radio button + void renderRadioButtonBackground(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, bool sunken) const; + + //* radio button + void renderRadioButton(QPainter *painter, + const QRect &rect, + const QColor &background, + const QColor &outline, + const QColor &tickColor, + bool sunken, + bool enabled, + RadioButtonState state, + qreal animation = AnimationData::OpacityInvalid, + bool mouseOver = false, + bool darkMode = false, + bool inMenu = false) const; + + //* slider groove + void renderSliderGroove(QPainter *painter, const QRect &rect, const QColor &) const; + + //* slider handle + void renderSliderHandle(QPainter *painter, const QRect &rect, const QColor &, const QColor &outline, const QColor &shadow, bool sunken, bool enabled, Side ticks, qreal angle = 0.0, bool darkMode = false) const; + + //* dial groove + void renderDialGroove(QPainter *painter, const QRect &rect, const QColor &) const; + + //* dial groove + void renderDialContents(QPainter *painter, const QRect &rect, const QColor &, qreal first, qreal second) const; + + //* progress bar groove + void renderProgressBarGroove(QPainter *painter, const QRect &rect, const QColor &, const QColor &) const; + + //* progress bar contents + void renderProgressBarContents(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline) const + { + return renderProgressBarGroove(painter, rect, color, outline); + } + + //* progress bar contents (animated) + void renderProgressBarBusyContents(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, bool horizontal, bool reverse, int progress) const; + + //* scrollbar groove + void renderScrollBarGroove(QPainter *painter, const QRect &rect, const QColor &color) const + { + return renderScrollBarHandle(painter, rect, color); + } + + //* scrollbar handle + void renderScrollBarHandle(QPainter *painter, const QRect &rect, const QColor &) const; + + //* toolbar handle + void renderToolBarHandle(QPainter *painter, const QRect &rect, const QColor &color) const + { + return renderProgressBarGroove(painter, rect, color, Qt::transparent); + } + + //* tabbar tab + void renderTabBarTab(QPainter *painter, const QRect &rect, const QColor &background, const QColor &color, const QColor &outline, Corners, bool renderFrame) const; + + //* generic arrow + void renderArrow(QPainter *painter, const QRect &rect, const QColor &, ArrowOrientation) const; + + //* generic sign (+-) + void renderSign(QPainter *painter, const QRect &rect, const QColor &color, bool orientation) const; + + //* generic button (for mdi decorations, tabs and dock widgets) + void renderDecorationButton(QPainter *painter, const QRect &rect, const QColor &, ButtonType, bool inverted) const; + + //@} + + //*@name compositing utilities + //@{ + + //* true if style was compiled for and is running on X11 + static bool isX11(void); + + //* true if running on platform Wayland + static bool isWayland(void); + + //* returns true if compositing is active + bool compositingActive(void) const; + + //* returns true if a given widget supports alpha channel + bool hasAlphaChannel(const QWidget *) const; + + //@} + + //@name high dpi utility functions + //@{ + + //* return dpi-aware pixmap of given size + virtual QPixmap highDpiPixmap(const QSize &size) const + { + return highDpiPixmap(size.width(), size.height()); + } + + //* return dpi-aware pixmap of given size + virtual QPixmap highDpiPixmap(int width) const + { + return highDpiPixmap(width, width); + } + + //* return dpi-aware pixmap of given size + virtual QPixmap highDpiPixmap(int width, int height) const; + + //* return device pixel ratio for a given pixmap + virtual qreal devicePixelRatio(const QPixmap &) const; + + //@} + + //*@name X11 utilities + //@{ + +#if ADWAITA_HAVE_X11 + + //* get xcb connection + static xcb_connection_t *connection(void); + + //* create xcb atom + xcb_atom_t createAtom(const QString &) const; + +#endif + + //@} + + //* frame radius + qreal frameRadius(qreal bias = 0) const + { + return qMax(qreal(Metrics::Frame_FrameRadius) - 0.5 + bias, 0.0); + } + + void setVariant(QWidget *widget, const QByteArray &variant); + +protected: + //* initialize + void init(void); + + //* return rectangle for widgets shadow, offset depending on light source + QRectF shadowRect(const QRectF &) const; + + //* return rounded path in a given rect, with only selected corners rounded, and for a given radius + QPainterPath roundedPath(const QRectF &, Corners, qreal) const; + +private: +#if ADWAITA_HAVE_X11 + + //* atom used for compositing manager + xcb_atom_t _compositingManagerAtom; + +#endif +}; + +} + +#endif diff --git a/src/style/adwaitastyle.cpp b/src/style/adwaitastyle.cpp new file mode 100644 index 0000000..260b330 --- /dev/null +++ b/src/style/adwaitastyle.cpp @@ -0,0 +1,7571 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * Copyright (C) 2014-2018 Martin Bříza * + * Copyright (C) 2019-2020 Jan Grulich * + * * + * 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 . * + *************************************************************************/ + +// Adwaita style +#include "adwaitastyle.h" +#include "adwaitahelper.h" + +// Adwaita lib +#include "animations/adwaitaanimations.h" +#include "adwaita.h" +#include "adwaitacolors.h" +#include "adwaitamnemonics.h" +#include "adwaitasplitterproxy.h" +#include "adwaitawidgetexplorer.h" +#include "adwaitawindowmanager.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 + +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + +namespace AdwaitaPrivate +{ + +// needed to keep track of tabbars when being dragged +class TabBarData: public QObject +{ +public: + //* constructor + explicit TabBarData(QObject *parent) + : QObject(parent) + {} + + //* destructor + virtual ~TabBarData(void) + {} + + //* assign target tabBar + void lock(const QWidget *widget) + { + _tabBar = widget; + } + + //* true if tabbar is locked + bool isLocked(const QWidget *widget) const + { + return _tabBar && _tabBar.data() == widget; + } + + //* release + void release(void) + { + _tabBar.clear(); + } + +private: + //* pointer to target tabBar + Adwaita::WeakPointer _tabBar; +}; + +//* needed to have spacing added to items in combobox +class ComboBoxItemDelegate: public QItemDelegate +{ +public: + //* constructor + ComboBoxItemDelegate(QAbstractItemView *parent) + : QItemDelegate(parent) + , _proxy(parent->itemDelegate()) + , _itemMargin(Adwaita::Metrics::ItemView_ItemMarginWidth) + {} + + //* destructor + virtual ~ComboBoxItemDelegate(void) + {} + + //* paint + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + // call either proxy or parent class + if (_proxy) { + _proxy.data()->paint(painter, option, index); + } else { + QItemDelegate::paint(painter, option, index); + } + } + + //* size hint for index + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const + { + // get size from either proxy or parent class + QSize size(_proxy ? + _proxy.data()->sizeHint(option, index) : + QItemDelegate::sizeHint(option, index)); + + // adjust and return + if (size.isValid()) { + size.rheight() += _itemMargin * 2; + } + + return size; + } + +private: + //* proxy + Adwaita::WeakPointer _proxy; + + //* margin + int _itemMargin; +}; + +} // namespace AdwaitaPrivate + +void tabLayout(const QStyleOptionTabV3 *opt, const QWidget *widget, QRect *textRect, QRect *iconRect, const QStyle *proxyStyle) +{ + Q_ASSERT(textRect); + Q_ASSERT(iconRect); + QRect tr = opt->rect; + bool verticalTabs = opt->shape == QTabBar::RoundedEast + || opt->shape == QTabBar::RoundedWest + || opt->shape == QTabBar::TriangularEast + || opt->shape == QTabBar::TriangularWest; + if (verticalTabs) + tr.setRect(0, 0, tr.height(), tr.width()); //0, 0 as we will have a translate transform + + int verticalShift = proxyStyle->pixelMetric(QStyle::PM_TabBarTabShiftVertical, opt, widget); + int horizontalShift = proxyStyle->pixelMetric(QStyle::PM_TabBarTabShiftHorizontal, opt, widget); + int hpadding = proxyStyle->pixelMetric(QStyle::PM_TabBarTabHSpace, opt, widget) / 2; + int vpadding = proxyStyle->pixelMetric(QStyle::PM_TabBarTabVSpace, opt, widget) / 2; + if (opt->shape == QTabBar::RoundedSouth || opt->shape == QTabBar::TriangularSouth) + verticalShift = -verticalShift; + tr.adjust(hpadding, verticalShift - vpadding, horizontalShift - hpadding, vpadding); + bool selected = opt->state & QStyle::State_Selected; + if (selected) { + tr.setTop(tr.top() - verticalShift); + tr.setRight(tr.right() - horizontalShift); + } + + // left widget + if (!opt->leftButtonSize.isEmpty()) { + tr.setLeft(tr.left() + 4 + + (verticalTabs ? opt->leftButtonSize.height() : opt->leftButtonSize.width())); + } + // right widget + if (!opt->rightButtonSize.isEmpty()) { + tr.setRight(tr.right() - 4 - + (verticalTabs ? opt->rightButtonSize.height() : opt->rightButtonSize.width())); + } + + // icon + if (!opt->icon.isNull()) { + QSize iconSize = opt->iconSize; + if (!iconSize.isValid()) { + int iconExtent = proxyStyle->pixelMetric(QStyle::PM_SmallIconSize); + iconSize = QSize(iconExtent, iconExtent); + } + QSize tabIconSize = opt->icon.actualSize(iconSize, + (opt->state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled, + (opt->state & QStyle::State_Selected) ? QIcon::On : QIcon::Off); + + *iconRect = QRect(tr.left(), tr.center().y() - tabIconSize.height() / 2, + tabIconSize.width(), tabIconSize .height()); + if (!verticalTabs) { + *iconRect = proxyStyle->visualRect(opt->direction, opt->rect, *iconRect); + } + tr.setLeft(tr.left() + tabIconSize.width() + 4); + } + + if (!verticalTabs) { + tr = proxyStyle->visualRect(opt->direction, opt->rect, tr); + } + + *textRect = tr; +} + +namespace Adwaita +{ + +//______________________________________________________________ +Style::Style(bool dark) + : _addLineButtons(SingleButton) + , _subLineButtons(SingleButton) + , _helper(new Helper()) + , _animations(new Animations(this)) + , _mnemonics(new Mnemonics(this)) + , _windowManager(new WindowManager(this)) + , _splitterFactory(new SplitterFactory(this)) + , _widgetExplorer(new WidgetExplorer(this)) + , _tabBarData(new AdwaitaPrivate::TabBarData(this)) + , _dark(dark) +{ + // use DBus connection to update on adwaita configuration change + QDBusConnection dbus = QDBusConnection::sessionBus(); + dbus.connect(QString(), + QStringLiteral("/AdwaitaStyle"), + QStringLiteral("org.kde.Adwaita.Style"), + QStringLiteral("reparseConfiguration"), this, SLOT(configurationChanged())); + + dbus.connect(QString(), + QStringLiteral("/AdwaitaDecoration"), + QStringLiteral("org.kde.Adwaita.Style"), + QStringLiteral("reparseConfiguration"), this, SLOT(configurationChanged())); + + // Detect if running under KDE, if so set menus, etc, to have translucent background. + // For GNOME desktop, dont want translucent backgrounds otherwise no menu shadow is drawn. + _isKDE = qgetenv("XDG_CURRENT_DESKTOP").toLower() == "kde"; + _isGNOME = qgetenv("XDG_CURRENT_DESKTOP").toLower() == "gnome"; + + // call the slot directly; this initial call will set up things that also + // need to be reset when the system palette changes + loadConfiguration(); +} + +//______________________________________________________________ +Style::~Style(void) +{ + delete _helper; +} + +//______________________________________________________________ +void Style::polish(QWidget *widget) +{ + if (!widget) { + return; + } + + // register widget to animations + _animations->registerWidget(widget); + _windowManager->registerWidget(widget); + _splitterFactory->registerWidget(widget); + + // enable mouse over effects for all necessary widgets + if (qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + || widget->inherits("KTextEditor::View")) { + widget->setAttribute(Qt::WA_Hover); + } + + if (qobject_cast(widget)) { + qobject_cast(widget)->setDrawBase(true); + } + + // enforce translucency for drag and drop window + if (widget->testAttribute(Qt::WA_X11NetWmWindowTypeDND) && _helper->compositingActive()) { + widget->setAttribute(Qt::WA_TranslucentBackground); + widget->clearMask(); + } + + // scrollarea polishing is somewhat complex. It is moved to a dedicated method + polishScrollArea(qobject_cast(widget)); + + if (QAbstractItemView *itemView = qobject_cast(widget)) { + // enable mouse over effects in itemviews' viewport + itemView->viewport()->setAttribute(Qt::WA_Hover); + } else if (QGroupBox *groupBox = qobject_cast(widget)) { + // checkable group boxes + if (groupBox->isCheckable()) { + groupBox->setAttribute(Qt::WA_Hover); + } + } else if (qobject_cast(widget) && qobject_cast(widget->parent())) { + widget->setAttribute(Qt::WA_Hover); + } else if (qobject_cast(widget) && qobject_cast(widget->parent())) { + widget->setAttribute(Qt::WA_Hover); + } else if (qobject_cast(widget) && widget->parent() && widget->parent()->inherits("KTitleWidget")) { + widget->setAutoFillBackground(false); + if (!Adwaita::Config::TitleWidgetDrawFrame) { + widget->setBackgroundRole(QPalette::Window); + } + } + + if (qobject_cast(widget)) { + // remove opaque painting for scrollbars + widget->setAttribute(Qt::WA_OpaquePaintEvent, false); + } else if (widget->inherits("KTextEditor::View")) { + addEventFilter(widget); + } else if (QToolButton *toolButton = qobject_cast(widget)) { + if (toolButton->autoRaise()) { + // for flat toolbuttons, adjust foreground and background role accordingly + widget->setBackgroundRole(QPalette::NoRole); + widget->setForegroundRole(QPalette::WindowText); + } + + if (widget->parentWidget() && + widget->parentWidget()->parentWidget() && + widget->parentWidget()->parentWidget()->inherits("Gwenview::SideBarGroup")) { + widget->setProperty(PropertyNames::toolButtonAlignment, Qt::AlignLeft); + } + } else if (qobject_cast(widget)) { + // add event filter on dock widgets + // and alter palette + widget->setAutoFillBackground(false); + widget->setContentsMargins(Metrics::Frame_FrameWidth, Metrics::Frame_FrameWidth, Metrics::Frame_FrameWidth, Metrics::Frame_FrameWidth); + addEventFilter(widget); + } else if (qobject_cast(widget)) { + widget->setAutoFillBackground(false); + addEventFilter(widget); + } else if (qobject_cast(widget)) { + widget->setBackgroundRole(QPalette::NoRole); + widget->setAutoFillBackground(false); + } else if (widget->parentWidget() && widget->parentWidget()->parentWidget() && qobject_cast(widget->parentWidget()->parentWidget()->parentWidget())) { + widget->setBackgroundRole(QPalette::NoRole); + widget->setAutoFillBackground(false); + widget->parentWidget()->setAutoFillBackground(false); + } else if (qobject_cast(widget)) { + setTranslucentBackground(widget); + } else if (qobject_cast(widget)) { + addEventFilter(widget); + } else if (QComboBox *comboBox = qobject_cast(widget)) { + if (!hasParent(widget, "QWebView")) { + QAbstractItemView *itemView(comboBox->view()); + if (itemView && itemView->itemDelegate() && itemView->itemDelegate()->inherits("QComboBoxDelegate")) { + itemView->setItemDelegate(new AdwaitaPrivate::ComboBoxItemDelegate(itemView)); + } + if (comboBox->isEditable()) { + QLineEdit *lineEdit = comboBox->lineEdit(); + if (lineEdit && !comboBox->isEnabled()) { + QPalette pal = lineEdit->palette(); + pal.setColor(QPalette::Base, comboBox->palette().color(QPalette::Window)); + lineEdit->setPalette(pal); + lineEdit->setAutoFillBackground(true); + } + } + } + } else if (widget->inherits("QComboBoxPrivateContainer")) { + addEventFilter(widget); + setTranslucentBackground(widget); + } else if (widget->inherits("QTipLabel")) { + setTranslucentBackground(widget); + } else if (QLineEdit *lineEdit = qobject_cast(widget)) { + // Do not use additional margin if the QLineEdit is really small + const bool useMarginWidth = lineEdit->width() > lineEdit->fontMetrics().width("#####"); + const bool useMarginHeight = lineEdit->height() > lineEdit->fontMetrics().height() + (2 * Metrics::LineEdit_MarginHeight); + const int marginHeight = useMarginHeight ? Metrics::LineEdit_MarginHeight : 0; + const int marginWidth = useMarginWidth ? Metrics::LineEdit_MarginWidth : 0; + lineEdit->setTextMargins(marginWidth, marginHeight, marginWidth, marginHeight); + } else if (QSpinBox *spinBox = qobject_cast(widget)) { + if (!spinBox->isEnabled()) { + QPalette pal = spinBox->palette(); + pal.setColor(QPalette::Base, spinBox->palette().color(QPalette::Window)); + spinBox->setPalette(pal); + spinBox->setAutoFillBackground(true); + } + } + + // HACK to avoid different text color in unfocused views + // This has a side effect that the view will never grey out, but it's still better then having + // views greyed out when the application is active + if (QPointer view = qobject_cast(widget)) { + QPalette pal = view->palette(); + // TODO keep synced with the standard palette + const QColor activeTextColor = _dark ? QColor("#eeeeec") : QColor("#2e3436"); + const QColor inactiveTextColor = _dark ? Colors::mix(QColor("#eeeeec"), Colors::darken(Colors::desaturate(QColor("#3d3846"), 1.0), 0.04)) : + Colors::mix(QColor("#2e3436"), QColor("#f6f5f4")); + // No custom text color used, we can do our HACK + if (inactiveTextColor == pal.color(QPalette::Inactive, QPalette::Text) && activeTextColor == pal.color(QPalette::Active, QPalette::Text)) { + pal.setColor(QPalette::Inactive, QPalette::Text, pal.color(QPalette::Active, QPalette::Text)); + view->setPalette(pal); + } + } + + if (!widget->parent() || !qobject_cast(widget->parent()) || qobject_cast(widget) || qobject_cast(widget)) { + addEventFilter(widget); + } + + // base class polishing + ParentStyleClass::polish(widget); +} + +//______________________________________________________________ +void Style::polishScrollArea(QAbstractScrollArea *scrollArea) +{ + // check argument + if (!scrollArea) { + return; + } + + // enable mouse over effect in sunken scrollareas that support focus + if (scrollArea->frameShadow() == QFrame::Sunken && scrollArea->focusPolicy() & Qt::StrongFocus) { + scrollArea->setAttribute(Qt::WA_Hover); + } + + if (scrollArea->viewport() && scrollArea->inherits("KItemListContainer") && scrollArea->frameShape() == QFrame::NoFrame) { + scrollArea->viewport()->setBackgroundRole(QPalette::Window); + scrollArea->viewport()->setForegroundRole(QPalette::WindowText); + } + + // add event filter, to make sure proper background is rendered behind scrollbars + addEventFilter(scrollArea); + + // force side panels as flat, on option + if (scrollArea->inherits("KDEPrivate::KPageListView") || scrollArea->inherits("KDEPrivate::KPageTreeView")) { + scrollArea->setProperty(PropertyNames::sidePanelView, true); + } + + // for all side view panels, unbold font (design choice) + if (scrollArea->property(PropertyNames::sidePanelView).toBool()) { + // upbold list font + QFont font(scrollArea->font()); + font.setBold(false); + scrollArea->setFont(font); + + // adjust background role + if (!Adwaita::Config::SidePanelDrawFrame) { + scrollArea->setBackgroundRole(QPalette::Window); + scrollArea->setForegroundRole(QPalette::WindowText); + + if (scrollArea->viewport()) { + scrollArea->viewport()->setBackgroundRole(QPalette::Window); + scrollArea->viewport()->setForegroundRole(QPalette::WindowText); + } + } + } + + // disable autofill background for flat (== NoFrame) scrollareas, with QPalette::Window as a background + // this fixes flat scrollareas placed in a tinted widget, such as groupboxes, tabwidgets or framed dock-widgets + if (!(scrollArea->frameShape() == QFrame::NoFrame || scrollArea->backgroundRole() == QPalette::Window)) { + return; + } + + // get viewport and check background role + QWidget *viewport(scrollArea->viewport()); + if (!(viewport && viewport->backgroundRole() == QPalette::Window)) { + return; + } + + // change viewport autoFill background. + // do the same for all children if the background role is QPalette::Window + viewport->setAutoFillBackground(false); + QList children(viewport->findChildren()); + foreach (QWidget *child, children) { + if (child->parent() == viewport && child->backgroundRole() == QPalette::Window) { + child->setAutoFillBackground(false); + } + } +} + +//_______________________________________________________________ +void Style::unpolish(QWidget *widget) +{ + // register widget to animations + _animations->unregisterWidget(widget); + _windowManager->unregisterWidget(widget); + _splitterFactory->unregisterWidget(widget); + + // remove event filter + if (qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + || widget->inherits("QComboBoxPrivateContainer") + || qobject_cast(widget) + || qobject_cast(widget)) { + widget->removeEventFilter(this); + } + + ParentStyleClass::unpolish(widget); +} + +void Style::polish(QPalette &palette) +{ + // TODO: highcontrast + palette = Colors::palette(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); +} + +QPalette Style::standardPalette() const +{ + // TODO: highcontrast + return Colors::palette(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); +} + +//______________________________________________________________ +int Style::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const +{ + // handle special cases + switch (metric) { + + // frame width + case PM_DefaultFrameWidth: + if (widget && widget->inherits("QComboBoxPrivateContainer")) { + return 1; + } + + if (qobject_cast(widget)) { + return Metrics::Menu_FrameWidth; + } + + if (qobject_cast(widget)) { + return Metrics::LineEdit_FrameWidth; + } + + if (qobject_cast(widget)) { + return Metrics::ScrollArea_FrameWidth; + } else if (isQtQuickControl(option, widget)) { + const QString &elementType = option->styleObject->property("elementType").toString(); + if (elementType == QLatin1String("edit") || elementType == QLatin1String("spinbox")) { + return Metrics::LineEdit_FrameWidth; + } else if (elementType == QLatin1String("combobox")) { + return Metrics::ComboBox_FrameWidth; + } + } + // fallback + return Metrics::Frame_FrameWidth; + case PM_ComboBoxFrameWidth: { + const QStyleOptionComboBox *comboBoxOption(qstyleoption_cast< const QStyleOptionComboBox *>(option)); + return comboBoxOption && comboBoxOption->editable ? Metrics::LineEdit_FrameWidth : Metrics::ComboBox_FrameWidth; + } + case PM_SpinBoxFrameWidth: + return Metrics::SpinBox_FrameWidth; + case PM_ToolBarFrameWidth: + return Metrics::ToolBar_FrameWidth; + case PM_ToolTipLabelFrameWidth: + return Metrics::ToolTip_FrameWidth; + + // layout + case PM_LayoutLeftMargin: + case PM_LayoutTopMargin: + case PM_LayoutRightMargin: + case PM_LayoutBottomMargin: { + /* + * use either Child margin or TopLevel margin, + * depending on widget type + */ + if ((option && (option->state & QStyle::State_Window)) || (widget && widget->isWindow())) { + return Metrics::Layout_TopLevelMarginWidth; + } else if (widget && widget->inherits("KPageView")) { + return 0; + } else { + return Metrics::Layout_ChildMarginWidth; + } + } + case PM_LayoutHorizontalSpacing: + return Metrics::Layout_DefaultSpacing; + case PM_LayoutVerticalSpacing: + return Metrics::Layout_DefaultSpacing; + + // buttons + case PM_ButtonMargin: { + // needs special case for kcalc buttons, to prevent the application to set too small margins + if (widget && widget->inherits("KCalcButton")) { + return Metrics::Button_MarginWidth + 4; + } else { + return Metrics::Button_MarginWidth; + } + } + + case PM_ButtonDefaultIndicator: + return 0; + case PM_ButtonShiftHorizontal: + return 0; + case PM_ButtonShiftVertical: + return 0; + + // menubars + case PM_MenuBarPanelWidth: + return 0; + case PM_MenuBarHMargin: + return 0; + case PM_MenuBarVMargin: + return 0; + case PM_MenuBarItemSpacing: + return 0; + case PM_MenuDesktopFrameWidth: + return 0; + + // menu buttons + case PM_MenuButtonIndicator: + return Metrics::MenuButton_IndicatorWidth; + case PM_MenuVMargin: + return 2; + case PM_MenuHMargin: + return _isGNOME ? 0 : 1; + + // toolbars + case PM_ToolBarHandleExtent: + return Metrics::ToolBar_HandleExtent; + case PM_ToolBarSeparatorExtent: + return Metrics::ToolBar_SeparatorWidth; + case PM_ToolBarExtensionExtent: + return pixelMetric(PM_SmallIconSize, option, widget) + 2 * Metrics::ToolButton_MarginWidth; + + case PM_ToolBarItemMargin: + return 0; + case PM_ToolBarItemSpacing: + return Metrics::ToolBar_ItemSpacing; + + // tabbars + case PM_TabBarTabShiftVertical: + return 0; + case PM_TabBarTabShiftHorizontal: + return 0; + case PM_TabBarTabOverlap: + return Metrics::TabBar_TabOverlap; + case PM_TabBarBaseOverlap: + return Metrics::TabBar_BaseOverlap; + case PM_TabBarTabHSpace: + return 2 * Metrics::TabBar_TabMarginWidth; + case PM_TabBarTabVSpace: + return 2 * Metrics::TabBar_TabMarginHeight; + case PM_TabCloseIndicatorWidth: + case PM_TabCloseIndicatorHeight: + return pixelMetric(PM_SmallIconSize, option, widget); + + // scrollbars + case PM_ScrollBarExtent: + return Metrics::ScrollBar_Extend; + case PM_ScrollBarSliderMin: + return Metrics::ScrollBar_MinSliderHeight; + + // title bar + case PM_TitleBarHeight: + return 2 * Metrics::TitleBar_MarginWidth + pixelMetric(PM_SmallIconSize, option, widget); + + // sliders + case PM_SliderThickness: + return Metrics::Slider_ControlThickness; + case PM_SliderControlThickness: + return Metrics::Slider_ControlThickness; + case PM_SliderLength: + return Metrics::Slider_ControlThickness; + + // checkboxes and radio buttons + case PM_IndicatorWidth: + return Metrics::CheckBox_Size; + case PM_IndicatorHeight: + return Metrics::CheckBox_Size; + case PM_ExclusiveIndicatorWidth: + return Metrics::CheckBox_Size; + case PM_ExclusiveIndicatorHeight: + return Metrics::CheckBox_Size; + + // list heaaders + case PM_HeaderMarkSize: + return Metrics::Header_ArrowSize; + case PM_HeaderMargin: + return Metrics::Header_MarginWidth; + + // dock widget + // return 0 here, since frame is handled directly in polish + case PM_DockWidgetFrameWidth: + return 0; + case PM_DockWidgetTitleMargin: + return Metrics::Frame_FrameWidth; + case PM_DockWidgetTitleBarButtonMargin: + return Metrics::ToolButton_MarginWidth; + + case PM_SplitterWidth: + return Metrics::Splitter_SplitterWidth; + case PM_DockWidgetSeparatorExtent: + return Metrics::Splitter_SplitterWidth; + // fallback + default: + return ParentStyleClass::pixelMetric(metric, option, widget); + } +} + +//______________________________________________________________ +int Style::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const +{ + switch (hint) { + case SH_RubberBand_Mask: { + if (QStyleHintReturnMask *mask = qstyleoption_cast(returnData)) { + mask->region = option->rect; + + /* + * need to check on widget before removing inner region + * in order to still preserve rubberband in MainWindow and QGraphicsView + * in QMainWindow because it looks better + * in QGraphicsView because the painting fails completely otherwise + */ + if (widget && (qobject_cast(widget->parent()) + || qobject_cast(widget->parent()) + || qobject_cast(widget->parent()))) { + return true; + } + + // also check if widget's parent is some itemView viewport + if (widget && widget->parent() && qobject_cast(widget->parent()->parent()) + && static_cast(widget->parent()->parent())->viewport() == widget->parent()) { + return true; + } + + // mask out center + mask->region -= insideMargin(option->rect, 1); + + return true; + } + return false; + } + + case SH_ComboBox_ListMouseTracking: + return true; + case SH_MenuBar_MouseTracking: + return true; + case SH_Menu_MouseTracking: + return true; + case SH_Menu_SubMenuPopupDelay: + return 150; + case SH_Menu_SloppySubMenus: + return true; + case SH_Widget_Animate: + return Adwaita::Config::AnimationsEnabled; + case SH_Menu_SupportsSections: + return true; + case SH_DialogButtonBox_ButtonsHaveIcons: + return false; + case SH_GroupBox_TextLabelVerticalAlignment: + return Qt::AlignVCenter; + case SH_TabBar_Alignment: + return Adwaita::Config::TabBarDrawCenteredTabs ? Qt::AlignCenter : Qt::AlignLeft; + case SH_ToolBox_SelectedPageTitleBold: + return false; + case SH_ScrollBar_MiddleClickAbsolutePosition: + return true; + case SH_ScrollView_FrameOnlyAroundContents: + return false; + case SH_FormLayoutFormAlignment: + return Qt::AlignLeft | Qt::AlignTop; + case SH_FormLayoutLabelAlignment: + return Qt::AlignRight; + case SH_FormLayoutFieldGrowthPolicy: + return QFormLayout::ExpandingFieldsGrow; + case SH_FormLayoutWrapPolicy: + return QFormLayout::DontWrapRows; + case SH_MessageBox_TextInteractionFlags: + return Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse; + case SH_ProgressDialog_CenterCancelButton: + return false; + case SH_MessageBox_CenterButtons: + return false; + case SH_RequestSoftwareInputPanel: + return RSIP_OnMouseClick; + case SH_TitleBar_NoBorder: + return true; + case SH_DockWidget_ButtonsHaveFrame: + return false; + case SH_ToolTipLabel_Opacity: + return 204 ;// Should have 30% transparency + default: + return ParentStyleClass::styleHint(hint, option, widget, returnData); + } +} + +//______________________________________________________________ +QRect Style::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const +{ + switch (element) { + case SE_PushButtonContents: + return pushButtonContentsRect(option, widget); + case SE_PushButtonFocusRect: + return pushButtonFocusRect(option, widget); + case SE_CheckBoxContents: + return checkBoxContentsRect(option, widget); + case SE_CheckBoxIndicator: + return checkBoxIndicatorRect(option, widget); + case SE_CheckBoxFocusRect: + return checkBoxFocusRect(option, widget); + case SE_RadioButtonContents: + return checkBoxContentsRect(option, widget); + case SE_RadioButtonIndicator: + return checkBoxIndicatorRect(option, widget); + case SE_RadioButtonFocusRect: + return checkBoxFocusRect(option, widget); + case SE_LineEditContents: + return lineEditContentsRect(option, widget); + case SE_ProgressBarGroove: + return progressBarGrooveRect(option, widget); + case SE_ProgressBarContents: + return progressBarContentsRect(option, widget); + case SE_ProgressBarLabel: + return progressBarLabelRect(option, widget); + case SE_HeaderArrow: + return headerArrowRect(option, widget); + case SE_HeaderLabel: + return headerLabelRect(option, widget); + case SE_SliderFocusRect: + return sliderFocusRect(option, widget); + case SE_TabBarTabLeftButton: + return tabBarTabLeftButtonRect(option, widget); + case SE_TabBarTabRightButton: + return tabBarTabRightButtonRect(option, widget); + case SE_TabWidgetTabBar: + return tabWidgetTabBarRect(option, widget); + case SE_TabWidgetTabContents: + return tabWidgetTabContentsRect(option, widget); + case SE_TabWidgetTabPane: + return tabWidgetTabPaneRect(option, widget); + case SE_TabWidgetLeftCorner: + return tabWidgetCornerRect(SE_TabWidgetLeftCorner, option, widget); + case SE_TabWidgetRightCorner: + return tabWidgetCornerRect(SE_TabWidgetRightCorner, option, widget); + case SE_ToolBoxTabContents: + return toolBoxTabContentsRect(option, widget); + // fallback + default: + return ParentStyleClass::subElementRect(element, option, widget); + } +} + +//______________________________________________________________ +QRect Style::subControlRect(ComplexControl element, const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const +{ + switch (element) { + case CC_GroupBox: + return groupBoxSubControlRect(option, subControl, widget); + case CC_ToolButton: + return toolButtonSubControlRect(option, subControl, widget); + case CC_ComboBox: + return comboBoxSubControlRect(option, subControl, widget); + case CC_SpinBox: + return spinBoxSubControlRect(option, subControl, widget); + case CC_ScrollBar: + return scrollBarSubControlRect(option, subControl, widget); + case CC_Dial: + return dialSubControlRect(option, subControl, widget); + case CC_Slider: + return sliderSubControlRect(option, subControl, widget); + + // fallback + default: + return ParentStyleClass::subControlRect(element, option, subControl, widget); + } +} + +//______________________________________________________________ +QSize Style::sizeFromContents(ContentsType element, const QStyleOption *option, const QSize &size, const QWidget *widget) const +{ + switch (element) { + case CT_CheckBox: + return checkBoxSizeFromContents(option, size, widget); + case CT_RadioButton: + return checkBoxSizeFromContents(option, size, widget); + case CT_LineEdit: + return lineEditSizeFromContents(option, size, widget); + case CT_ComboBox: + return comboBoxSizeFromContents(option, size, widget); + case CT_SpinBox: + return spinBoxSizeFromContents(option, size, widget); + case CT_Slider: + return sliderSizeFromContents(option, size, widget); + case CT_PushButton: + return pushButtonSizeFromContents(option, size, widget); + case CT_ToolButton: + return toolButtonSizeFromContents(option, size, widget); + case CT_MenuBar: + return defaultSizeFromContents(option, size, widget); + case CT_MenuBarItem: + return menuBarItemSizeFromContents(option, size, widget); + case CT_MenuItem: + return menuItemSizeFromContents(option, size, widget); + case CT_ProgressBar: + return progressBarSizeFromContents(option, size, widget); + case CT_TabWidget: + return tabWidgetSizeFromContents(option, size, widget); + case CT_TabBarTab: + return tabBarTabSizeFromContents(option, size, widget); + case CT_HeaderSection: + return headerSectionSizeFromContents(option, size, widget); + case CT_ItemViewItem: + return itemViewItemSizeFromContents(option, size, widget); + + // fallback + default: + return ParentStyleClass::sizeFromContents(element, option, size, widget); + } + +} + +//______________________________________________________________ +QStyle::SubControl Style::hitTestComplexControl(ComplexControl control, const QStyleOptionComplex *option, const QPoint &point, const QWidget *widget) const +{ + switch (control) { + case CC_ScrollBar: { + QRect grooveRect = subControlRect(CC_ScrollBar, option, SC_ScrollBarGroove, widget); + if (grooveRect.contains(point)) { + // Must be either page up/page down, or just click on the slider. + QRect sliderRect = subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget); + + if (sliderRect.contains(point)) { + return SC_ScrollBarSlider; + } else if (preceeds(point, sliderRect, option)) { + return SC_ScrollBarSubPage; + } else { + return SC_ScrollBarAddPage; + } + } + + // This is one of the up/down buttons. First, decide which one it is. + if (preceeds(point, grooveRect, option)) { + if (_subLineButtons == DoubleButton) { + QRect buttonRect = scrollBarInternalSubControlRect(option, SC_ScrollBarSubLine); + return scrollBarHitTest(buttonRect, point, option); + } else { + return SC_ScrollBarSubLine; + } + } + + if (_addLineButtons == DoubleButton) { + QRect buttonRect = scrollBarInternalSubControlRect(option, SC_ScrollBarAddLine); + return scrollBarHitTest(buttonRect, point, option); + } else { + return SC_ScrollBarAddLine; + } + } + + // fallback + default: + return ParentStyleClass::hitTestComplexControl(control, option, point, widget); + } +} + +//______________________________________________________________ +void Style::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + StylePrimitive fcn(nullptr); + switch (element) { + case PE_PanelButtonCommand: + fcn = &Style::drawPanelButtonCommandPrimitive; + break; + case PE_PanelButtonTool: + fcn = &Style::drawPanelButtonToolPrimitive; + break; + case PE_PanelScrollAreaCorner: + fcn = &Style::drawPanelScrollAreaCornerPrimitive; + break; + case PE_PanelMenu: + fcn = &Style::drawPanelMenuPrimitive; + break; + case PE_PanelTipLabel: + fcn = &Style::drawPanelTipLabelPrimitive; + break; + case PE_PanelItemViewRow: + fcn = &Style::drawPanelItemViewRowPrimitive; + break; + case PE_PanelItemViewItem: + fcn = &Style::drawPanelItemViewItemPrimitive; + break; + case PE_IndicatorCheckBox: + fcn = &Style::drawIndicatorCheckBoxPrimitive; + break; + case PE_IndicatorRadioButton: + fcn = &Style::drawIndicatorRadioButtonPrimitive; + break; + case PE_IndicatorButtonDropDown: + fcn = &Style::drawIndicatorButtonDropDownPrimitive; + break; + case PE_IndicatorTabClose: + fcn = &Style::drawIndicatorTabClosePrimitive; + break; + case PE_IndicatorTabTear: + fcn = &Style::drawIndicatorTabTearPrimitive; + break; + case PE_IndicatorArrowUp: + fcn = &Style::drawIndicatorArrowUpPrimitive; + break; + case PE_IndicatorArrowDown: + fcn = &Style::drawIndicatorArrowDownPrimitive; + break; + case PE_IndicatorArrowLeft: + fcn = &Style::drawIndicatorArrowLeftPrimitive; + break; + case PE_IndicatorArrowRight: + fcn = &Style::drawIndicatorArrowRightPrimitive; + break; + case PE_IndicatorHeaderArrow: + fcn = &Style::drawIndicatorHeaderArrowPrimitive; + break; + case PE_IndicatorToolBarHandle: + fcn = &Style::drawIndicatorToolBarHandlePrimitive; + break; + case PE_IndicatorToolBarSeparator: + fcn = &Style::drawIndicatorToolBarSeparatorPrimitive; + break; + case PE_IndicatorBranch: + fcn = &Style::drawIndicatorBranchPrimitive; + break; + case PE_FrameStatusBar: + fcn = &Style::emptyPrimitive; + break; + case PE_Frame: + fcn = &Style::drawFramePrimitive; + break; + case PE_FrameLineEdit: + fcn = &Style::drawFrameLineEditPrimitive; + break; + case PE_FrameMenu: + fcn = &Style::drawFrameMenuPrimitive; + break; + case PE_FrameGroupBox: + fcn = &Style::drawFrameGroupBoxPrimitive; + break; + case PE_FrameTabWidget: + fcn = &Style::drawFrameTabWidgetPrimitive; + break; + case PE_FrameTabBarBase: + fcn = &Style::drawFrameTabBarBasePrimitive; + break; + case PE_FrameWindow: + fcn = &Style::drawFrameWindowPrimitive; + break; + case PE_FrameFocusRect: + fcn = _frameFocusPrimitive; + break; + // fallback + default: + break; + } + + painter->save(); + + // call function if implemented + if (!(fcn && (this->*fcn)(option, painter, widget))) { + ParentStyleClass::drawPrimitive(element, option, painter, widget); + } + + painter->restore(); +} + +//______________________________________________________________ +void Style::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + StyleControl fcn(nullptr); + switch (element) { + case CE_PushButtonBevel: + fcn = &Style::drawPanelButtonCommandPrimitive; + break; + case CE_PushButtonLabel: + fcn = &Style::drawPushButtonLabelControl; + break; + case CE_CheckBoxLabel: + fcn = &Style::drawCheckBoxLabelControl; + break; + case CE_RadioButtonLabel: + fcn = &Style::drawCheckBoxLabelControl; + break; + case CE_ToolButtonLabel: + fcn = &Style::drawToolButtonLabelControl; + break; + case CE_ComboBoxLabel: + fcn = &Style::drawComboBoxLabelControl; + break; + case CE_MenuBarEmptyArea: + fcn = &Style::drawMenuBarEmptyArea; + break; + case CE_MenuBarItem: + fcn = &Style::drawMenuBarItemControl; + break; + case CE_MenuItem: + fcn = &Style::drawMenuItemControl; + break; + case CE_ToolBar: + fcn = &Style::emptyControl; + break; + case CE_ProgressBar: + fcn = &Style::drawProgressBarControl; + break; + case CE_ProgressBarContents: + fcn = &Style::drawProgressBarContentsControl; + break; + case CE_ProgressBarGroove: + fcn = &Style::drawProgressBarGrooveControl; + break; + case CE_ProgressBarLabel: + fcn = &Style::drawProgressBarLabelControl; + break; + case CE_ScrollBarSlider: + fcn = &Style::drawScrollBarSliderControl; + break; + case CE_ScrollBarAddLine: + fcn = &Style::drawScrollBarAddLineControl; + break; + case CE_ScrollBarSubLine: + fcn = &Style::drawScrollBarSubLineControl; + break; + case CE_ScrollBarAddPage: + fcn = &Style::emptyControl; + break; + case CE_ScrollBarSubPage: + fcn = &Style::emptyControl; + break; + case CE_ShapedFrame: + fcn = &Style::drawShapedFrameControl; + break; + case CE_RubberBand: + fcn = &Style::drawRubberBandControl; + break; + case CE_SizeGrip: + fcn = &Style::emptyControl; + break; + case CE_HeaderSection: + fcn = &Style::drawHeaderSectionControl; + break; + case CE_HeaderLabel: + fcn = &Style::drawHeaderLabelControl; + break; + case CE_HeaderEmptyArea: + fcn = &Style::drawHeaderEmptyAreaControl; + break; + case CE_TabBarTabLabel: + fcn = &Style::drawTabBarTabLabelControl; + break; + case CE_TabBarTabShape: + fcn = &Style::drawTabBarTabShapeControl; + break; + case CE_ToolBoxTabLabel: + fcn = &Style::drawToolBoxTabLabelControl; + break; + case CE_ToolBoxTabShape: + fcn = &Style::drawToolBoxTabShapeControl; + break; + case CE_DockWidgetTitle: + fcn = &Style::drawDockWidgetTitleControl; + break; + case CE_ItemViewItem: + fcn = &Style::drawItemViewItemControl; + break; + // fallback + default: + break; + } + + painter->save(); + + // call function if implemented + if (!(fcn && (this->*fcn)(option, painter, widget))) { + ParentStyleClass::drawControl(element, option, painter, widget); + } + + painter->restore(); +} + +//______________________________________________________________ +void Style::drawComplexControl(ComplexControl element, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const +{ + StyleComplexControl fcn(nullptr); + switch (element) { + case CC_GroupBox: + fcn = &Style::drawGroupBoxComplexControl; + break; + case CC_ToolButton: + fcn = &Style::drawToolButtonComplexControl; + break; + case CC_ComboBox: + fcn = &Style::drawComboBoxComplexControl; + break; + case CC_SpinBox: + fcn = &Style::drawSpinBoxComplexControl; + break; + case CC_Slider: + fcn = &Style::drawSliderComplexControl; + break; + case CC_Dial: + fcn = &Style::drawDialComplexControl; + break; + case CC_ScrollBar: + fcn = &Style::drawScrollBarComplexControl; + break; + case CC_TitleBar: + fcn = &Style::drawTitleBarComplexControl; + break; + + // fallback + default: + break; + } + + painter->save(); + + // call function if implemented + if (!(fcn && (this->*fcn)(option, painter, widget))) { + ParentStyleClass::drawComplexControl(element, option, painter, widget); + } + + painter->restore(); +} + +//___________________________________________________________________________________ +void Style::drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &palette, bool enabled, + const QString &text, QPalette::ColorRole textRole) const +{ + // hide mnemonics if requested + if (!_mnemonics->enabled() && (flags & Qt::TextShowMnemonic) && !(flags & Qt::TextHideMnemonic)) { + flags &= ~Qt::TextShowMnemonic; + flags |= Qt::TextHideMnemonic; + } + + // make sure vertical alignment is defined + // fallback on Align::VCenter if not + if (!(flags & Qt::AlignVertical_Mask)) { + flags |= Qt::AlignVCenter; + } + + if (_animations->widgetEnabilityEngine().enabled()) { + /* + * check if painter engine is registered to WidgetEnabilityEngine, and animated + * if yes, merge the palettes. Note: a static_cast is safe here, since only the address + * of the pointer is used, not the actual content. + */ + const QWidget *widget(static_cast(painter->device())); + if (_animations->widgetEnabilityEngine().isAnimated(widget, AnimationEnable)) { + QPalette copy(Colors::disabledPalette(palette, _animations->widgetEnabilityEngine().opacity(widget, AnimationEnable))); + return ParentStyleClass::drawItemText(painter, rect, flags, copy, enabled, text, textRole); + } + } + + // fallback + return ParentStyleClass::drawItemText(painter, rect, flags, palette, enabled, text, textRole); +} + +//_____________________________________________________________________ +bool Style::eventFilter(QObject *object, QEvent *event) +{ + if (QDockWidget *dockWidget = qobject_cast(object)) { + return eventFilterDockWidget(dockWidget, event); + } else if (QMdiSubWindow *subWindow = qobject_cast(object)) { + return eventFilterMdiSubWindow(subWindow, event); + } else if (QCommandLinkButton *commandLinkButton = qobject_cast(object)) { + return eventFilterCommandLinkButton(commandLinkButton, event); + } + + // cast to QWidget + QWidget *widget = static_cast(object); + if (widget->inherits("QAbstractScrollArea") || widget->inherits("KTextEditor::View")) { + return eventFilterScrollArea(widget, event); + } else if (widget->inherits("QComboBoxPrivateContainer")) { + return eventFilterComboBoxContainer(widget, event); + } + + if ((!widget->parent() || !qobject_cast(widget->parent()) || qobject_cast(widget) || qobject_cast(widget)) + && (QEvent::Show == event->type() || QEvent::StyleChange == event->type())) { + _helper->setVariant(widget, _dark ? "dark" : "light"); + } + + // fallback + return ParentStyleClass::eventFilter(object, event); +} + +//____________________________________________________________________________ +bool Style::eventFilterScrollArea(QWidget *widget, QEvent *event) +{ + switch (event->type()) { + case QEvent::Paint: { + // get scrollarea viewport + QAbstractScrollArea *scrollArea(qobject_cast(widget)); + QWidget *viewport; + if (!(scrollArea && (viewport = scrollArea->viewport()))) { + break; + } + + // get scrollarea horizontal and vertical containers + QWidget *child(nullptr); + QList children; + if ((child = scrollArea->findChild("qt_scrollarea_vcontainer")) && child->isVisible()) { + children.append(child); + } + + if ((child = scrollArea->findChild("qt_scrollarea_hcontainer")) && child->isVisible()) { + children.append(child); + } + + if (children.empty()) { + break; + } + + if (!scrollArea->styleSheet().isEmpty()) { + break; + } + + // make sure proper background is rendered behind the containers + QPainter painter(scrollArea); + painter.setClipRegion(static_cast(event)->region()); + + painter.setPen(Qt::NoPen); + + // decide background color + const QPalette::ColorRole role(viewport->backgroundRole()); + QColor background; + if (role == QPalette::Window && hasAlteredBackground(viewport)) { + background = Colors::frameBackgroundColor(StyleOptions(viewport->palette())); + } else { + background = viewport->palette().color(role); + } + painter.setBrush(background); + + // render + foreach (auto *child, children) { + painter.drawRect(child->geometry()); + } + + break; + } + + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: { + // case event + QMouseEvent *mouseEvent(static_cast(event)); + + // get frame framewidth + int frameWidth(pixelMetric(PM_DefaultFrameWidth, 0, widget)); + + // find list of scrollbars + QList scrollBars; + if (QAbstractScrollArea *scrollArea = qobject_cast(widget)) { + if (scrollArea->horizontalScrollBarPolicy() != Qt::ScrollBarAlwaysOff) { + scrollBars.append(scrollArea->horizontalScrollBar()); + } + if (scrollArea->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff) { + scrollBars.append(scrollArea->verticalScrollBar()); + } + } else if (widget->inherits("KTextEditor::View")) { + scrollBars = widget->findChildren(); + } + + // loop over found scrollbars + foreach (QScrollBar *scrollBar, scrollBars) { + if (!(scrollBar && scrollBar->isVisible())) { + continue; + } + + QPoint offset; + if (scrollBar->orientation() == Qt::Horizontal) { + offset = QPoint(0, frameWidth); + } else { + offset = QPoint(QApplication::isLeftToRight() ? frameWidth : -frameWidth, 0); + } + + // map position to scrollarea + QPoint position(scrollBar->mapFrom(widget, mouseEvent->pos() - offset)); + + // check if contains + if (!scrollBar->rect().contains(position)) { + continue; + } + + // copy event, send and return + QMouseEvent copy(mouseEvent->type(), position, scrollBar->mapToGlobal(position), + mouseEvent->button(), mouseEvent->buttons(), mouseEvent->modifiers()); + + QCoreApplication::sendEvent(scrollBar, ©); + event->setAccepted(true); + return true; + } + + break; + } + + default: + break; + } + + return ParentStyleClass::eventFilter(widget, event); +} + +//_________________________________________________________ +bool Style::eventFilterComboBoxContainer(QWidget *widget, QEvent *event) +{ + if (event->type() == QEvent::Paint) { + QPainter painter(widget); + QPaintEvent *paintEvent = static_cast(event); + painter.setClipRegion(paintEvent->region()); + + QRect rect(widget->rect()); + const QPalette &palette(widget->palette()); + QColor background(Colors::frameBackgroundColor(StyleOptions(palette))); + QColor outline(Colors::frameOutlineColor(StyleOptions(palette))); + + bool hasAlpha(_helper->hasAlphaChannel(widget)); + if (hasAlpha) { + painter.setCompositionMode(QPainter::CompositionMode_Source); + _helper->renderMenuFrame(&painter, rect, background, outline, true); + } else { + _helper->renderMenuFrame(&painter, rect, background, outline, false); + } + } + + return false; +} + +//____________________________________________________________________________ +bool Style::eventFilterDockWidget(QDockWidget *dockWidget, QEvent *event) +{ + if (event->type() == QEvent::Paint) { + // create painter and clip + QPainter painter(dockWidget); + QPaintEvent *paintEvent = static_cast(event); + painter.setClipRegion(paintEvent->region()); + + // store palette and set colors + const QPalette &palette(dockWidget->palette()); + QColor background(Colors::frameBackgroundColor(StyleOptions(palette))); + QColor outline(Colors::frameOutlineColor(StyleOptions(palette))); + + // store rect + QRect rect(dockWidget->rect()); + + // render + if (dockWidget->isFloating()) { + _helper->renderMenuFrame(&painter, rect, background, outline, false); + } else if (Adwaita::Config::DockWidgetDrawFrame || (dockWidget->features() & QDockWidget::AllDockWidgetFeatures)) { + _helper->renderFrame(&painter, rect, background, outline); + } + } + + return false; +} + +//____________________________________________________________________________ +bool Style::eventFilterMdiSubWindow(QMdiSubWindow *subWindow, QEvent *event) +{ + if (event->type() == QEvent::Paint) { + QPainter painter(subWindow); + QPaintEvent *paintEvent(static_cast(event)); + painter.setClipRegion(paintEvent->region()); + + QRect rect(subWindow->rect()); + QColor background(subWindow->palette().color(QPalette::Window)); + + if (subWindow->isMaximized()) { + // full painting + painter.setPen(Qt::NoPen); + painter.setBrush(background); + painter.drawRect(rect); + } else { + // framed painting + _helper->renderMenuFrame(&painter, rect, background, QColor()); + } + } + + // continue with normal painting + return false; +} + +//____________________________________________________________________________ +bool Style::eventFilterCommandLinkButton(QCommandLinkButton *button, QEvent *event) +{ + if (event->type() == QEvent::Paint) { + // painter + QPainter painter(button); + painter.setClipRegion(static_cast(event)->region()); + + bool isFlat = false; + + // option + QStyleOptionButton option; + option.initFrom(button); + option.features |= QStyleOptionButton::CommandLinkButton; + if (isFlat) { + option.features |= QStyleOptionButton::Flat; + } + option.text = QString(); + option.icon = QIcon(); + + if (button->isChecked()) { + option.state |= State_On; + } + if (button->isDown()) { + option.state |= State_Sunken; + } + // frame + drawControl(QStyle::CE_PushButton, &option, &painter, button); + + // offset + int margin(Metrics::Button_MarginWidth + Metrics::Frame_FrameWidth); + QPoint offset(margin, margin); + + if (button->isDown() && !isFlat) + painter.translate(1, 1); + { + offset += QPoint(1, 1); + } + + // state + const State &state(option.state); + bool enabled(state & State_Enabled); + bool mouseOver((state & State_Active) && enabled && (state & State_MouseOver)); + bool hasFocus(enabled && (state & State_HasFocus)); + + // icon + if (!button->icon().isNull()) { + QSize pixmapSize(button->icon().actualSize(button->iconSize())); + QRect pixmapRect(QPoint(offset.x(), button->description().isEmpty() ? (button->height() - pixmapSize.height()) / 2 : offset.y()), pixmapSize); + const QPixmap pixmap(button->icon().pixmap(pixmapSize, + enabled ? QIcon::Normal : QIcon::Disabled, + button->isChecked() ? QIcon::On : QIcon::Off)); + drawItemPixmap(&painter, pixmapRect, Qt::AlignCenter, pixmap); + + offset.rx() += pixmapSize.width() + Metrics::Button_ItemSpacing; + } + + // text rect + QRect textRect(offset, QSize(button->size().width() - offset.x() - margin, button->size().height() - 2 * margin)); + const QPalette::ColorRole textRole = (enabled && hasFocus && !mouseOver && !isFlat) ? QPalette::HighlightedText : QPalette::ButtonText; + if (!button->text().isEmpty()) { + QFont font(button->font()); + font.setBold(true); + painter.setFont(font); + if (button->description().isEmpty()) { + drawItemText(&painter, textRect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextHideMnemonic, button->palette(), enabled, button->text(), textRole); + } else { + drawItemText(&painter, textRect, Qt::AlignLeft | Qt::AlignTop | Qt::TextHideMnemonic, button->palette(), enabled, button->text(), textRole); + textRect.setTop(textRect.top() + QFontMetrics(font).height()); + } + + painter.setFont(button->font()); + } + + if (!button->description().isEmpty()) { + drawItemText(&painter, textRect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, button->palette(), enabled, button->description(), textRole); + } + + return true; + } + + // continue with normal painting + return false; +} + +//_____________________________________________________________________ +void Style::configurationChanged(void) +{ + // reload configuration + loadConfiguration(); +} + +//____________________________________________________________________ +QIcon Style::standardIconImplementation(StandardPixmap standardPixmap, const QStyleOption *option, const QWidget *widget) const +{ + // lookup cache + if (_iconCache.contains(standardPixmap)) { + return _iconCache.value(standardPixmap); + } + + QIcon icon; + switch (standardPixmap) { + case SP_TitleBarNormalButton: + case SP_TitleBarMinButton: + case SP_TitleBarMaxButton: + case SP_TitleBarCloseButton: + case SP_DockWidgetCloseButton: + icon = titleBarButtonIcon(standardPixmap, option, widget); + break; + case SP_ToolBarHorizontalExtensionButton: + case SP_ToolBarVerticalExtensionButton: + icon = toolBarExtensionIcon(standardPixmap, option, widget); + break; + default: + break; + } + + if (icon.isNull()) { + // do not cache parent style icon, since it may change at runtime + return ParentStyleClass::standardIcon(standardPixmap, option, widget); + } else { + const_cast(&_iconCache)->insert(standardPixmap, icon); + return icon; + } +} + +//_____________________________________________________________________ +void Style::loadConfiguration() +{ + // reinitialize engines + _animations->setupEngines(); + _windowManager->initialize(); + + // mnemonics + _mnemonics->setMode(Adwaita::Config::MnemonicsMode); + + // splitter proxy + _splitterFactory->setEnabled(Adwaita::Config::SplitterProxyEnabled); + + // clear icon cache + _iconCache.clear(); + + // scrollbar buttons + switch (Adwaita::Config::ScrollBarAddLineButtons) { + case 0: + _addLineButtons = NoButton; + break; + case 1: + _addLineButtons = SingleButton; + break; + + default: + case 2: + _addLineButtons = DoubleButton; + break; + } + + switch (Adwaita::Config::ScrollBarSubLineButtons) { + case 0: + _subLineButtons = NoButton; + break; + case 1: + _subLineButtons = SingleButton; + break; + + default: + case 2: + _subLineButtons = DoubleButton; + break; + } + + // frame focus + if (Adwaita::Config::ViewDrawFocusIndicator) { + _frameFocusPrimitive = &Style::drawFrameFocusRectPrimitive; + } else { + _frameFocusPrimitive = &Style::emptyPrimitive; + } + + // widget explorer + _widgetExplorer->setEnabled(Adwaita::Config::WidgetExplorerEnabled); + _widgetExplorer->setDrawWidgetRects(Adwaita::Config::DrawWidgetRects); +} + +//___________________________________________________________________________________________________________________ +QRect Style::pushButtonContentsRect(const QStyleOption *option, const QWidget *) const +{ + return insideMargin(option->rect, Metrics::Frame_FrameWidth); +} + +//___________________________________________________________________________________________________________________ +QRect Style::pushButtonFocusRect(const QStyleOption *option, const QWidget *) const +{ + return insideMargin(option->rect, 3); +} + +//___________________________________________________________________________________________________________________ +QRect Style::checkBoxContentsRect(const QStyleOption *option, const QWidget *) const +{ + return visualRect(option, option->rect.adjusted(Metrics::CheckBox_Size + Metrics::CheckBox_ItemSpacing, 0, 0, 0)); +} + +//___________________________________________________________________________________________________________________ +QRect Style::checkBoxIndicatorRect(const QStyleOption *option, const QWidget *widget) const +{ + return ParentStyleClass::subElementRect(SE_CheckBoxIndicator, option, widget); +} + +//___________________________________________________________________________________________________________________ +QRect Style::checkBoxFocusRect(const QStyleOption *option, const QWidget *widget) const +{ + return QRect(option->rect.left() + 2, option->rect.top() + 1, + ParentStyleClass::subElementRect(SE_CheckBoxFocusRect, option, widget).right() - option->rect.left(), + option->rect.height() - 2); +} + +//___________________________________________________________________________________________________________________ +QRect Style::lineEditContentsRect(const QStyleOption *option, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionFrame *frameOption(qstyleoption_cast(option)); + if (!frameOption) { + return option->rect; + } + + // check flatness + bool flat(frameOption->lineWidth == 0); + if (flat) { + return option->rect; + } + + // copy rect and take out margins + QRect rect(option->rect); + + // take out margins if there is enough room + int frameWidth(pixelMetric(PM_DefaultFrameWidth, option, widget)); + if (rect.height() >= option->fontMetrics.height() + 2 * frameWidth) { + return insideMargin(rect, frameWidth); + } else { + return rect; + } +} + +//___________________________________________________________________________________________________________________ +QRect Style::progressBarGrooveRect(const QStyleOption *option, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionProgressBar *progressBarOption(qstyleoption_cast(option)); + if (!progressBarOption) { + return option->rect; + } + + // get flags and orientation + bool textVisible(progressBarOption->textVisible); + bool busy(progressBarOption->minimum == 0 && progressBarOption->maximum == 0); + + const QStyleOptionProgressBarV2 *progressBarOption2(qstyleoption_cast(option)); + bool horizontal(!progressBarOption2 || progressBarOption2->orientation == Qt::Horizontal); + + // copy rectangle and adjust + QRect rect(option->rect); + int frameWidth(pixelMetric(PM_DefaultFrameWidth, option, widget)); + if (horizontal) { + rect = insideMargin(rect, frameWidth, 0); + } else { + rect = insideMargin(rect, 0, frameWidth); + } + + if (textVisible && !busy && horizontal) { + QRect textRect(subElementRect(SE_ProgressBarLabel, option, widget)); + textRect = visualRect(option, textRect); + rect.setRight(textRect.left() - Metrics::ProgressBar_ItemSpacing - 1); + rect = visualRect(option, rect); + rect = centerRect(rect, rect.width(), Metrics::ProgressBar_Thickness); + } else if (horizontal) { + rect = centerRect(rect, rect.width(), Metrics::ProgressBar_Thickness); + } else { + rect = centerRect(rect, Metrics::ProgressBar_Thickness, rect.height()); + } + + return rect; +} + +//___________________________________________________________________________________________________________________ +QRect Style::progressBarContentsRect(const QStyleOption *option, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionProgressBar *progressBarOption(qstyleoption_cast(option)); + if (!progressBarOption) { + return QRect(); + } + + // get groove rect + QRect rect(progressBarGrooveRect(option, widget)); + + // in busy mode, grooveRect is used + bool busy(progressBarOption->minimum == 0 && progressBarOption->maximum == 0); + if (busy) { + return rect; + } + + // get orientation + const QStyleOptionProgressBarV2 *progressBarOption2(qstyleoption_cast(option)); + bool horizontal(!progressBarOption2 || progressBarOption2->orientation == Qt::Horizontal); + + // check inverted appearance + bool inverted(progressBarOption2 ? progressBarOption2->invertedAppearance : false); + + // get progress and steps + qreal progress(progressBarOption->progress - progressBarOption->minimum); + int steps(qMax(progressBarOption->maximum - progressBarOption->minimum, 1)); + + //Calculate width fraction + qreal widthFrac = qMin(qreal(1), progress / steps); + + // convert the pixel width + int indicatorSize(widthFrac * (horizontal ? rect.width() : rect.height())); + + QRect indicatorRect; + if (horizontal) { + indicatorRect = QRect(inverted ? (rect.right() - indicatorSize + 1) : rect.left(), rect.y(), indicatorSize, rect.height()); + indicatorRect = visualRect(option->direction, rect, indicatorRect); + } else { + indicatorRect = QRect(rect.x(), inverted ? rect.top() : (rect.bottom() - indicatorSize + 1), rect.width(), indicatorSize); + } + + return indicatorRect; +} + +//___________________________________________________________________________________________________________________ +QRect Style::progressBarLabelRect(const QStyleOption *option, const QWidget *) const +{ + // cast option and check + const QStyleOptionProgressBar *progressBarOption(qstyleoption_cast(option)); + if (!progressBarOption) { + return QRect(); + } + + // get flags and check + bool textVisible(progressBarOption->textVisible); + bool busy(progressBarOption->minimum == 0 && progressBarOption->maximum == 0); + if (!textVisible || busy) { + return QRect(); + } + + // get direction and check + const QStyleOptionProgressBarV2 *progressBarOption2(qstyleoption_cast(option)); + bool horizontal(!progressBarOption2 || progressBarOption2->orientation == Qt::Horizontal); + if (!horizontal) { + return QRect(); + } + + int textWidth = qMax(option->fontMetrics.size(_mnemonics->textFlags(), progressBarOption->text).width(), + option->fontMetrics.size(_mnemonics->textFlags(), QStringLiteral("100%")).width()); + + QRect rect(insideMargin(option->rect, Metrics::Frame_FrameWidth, 0)); + rect.setLeft(rect.right() - textWidth + 1); + rect = visualRect(option, rect); + + return rect; +} + +//___________________________________________________________________________________________________________________ +QRect Style::headerArrowRect(const QStyleOption *option, const QWidget *) const +{ + // cast option and check + const QStyleOptionHeader *headerOption(qstyleoption_cast(option)); + if (!headerOption) { + return option->rect; + } + + // check if arrow is necessary + if (headerOption->sortIndicator == QStyleOptionHeader::None) { + return QRect(); + } + + QRect arrowRect(insideMargin(option->rect, Metrics::Header_MarginWidth)); + arrowRect.setLeft(arrowRect.right() - Metrics::Header_ArrowSize + 1); + + return visualRect(option, arrowRect); +} + +//___________________________________________________________________________________________________________________ +QRect Style::headerLabelRect(const QStyleOption *option, const QWidget *) const +{ + // cast option and check + const QStyleOptionHeader *headerOption(qstyleoption_cast(option)); + if (!headerOption) { + return option->rect; + } + + // check if arrow is necessary + // QRect labelRect( insideMargin( option->rect, Metrics::Header_MarginWidth ) ); + QRect labelRect(insideMargin(option->rect, Metrics::Header_MarginWidth, 0)); + if (headerOption->sortIndicator == QStyleOptionHeader::None) { + return labelRect; + } + + labelRect.adjust(0, 0, -Metrics::Header_ArrowSize - Metrics::Header_ItemSpacing, 0); + return visualRect(option, labelRect); +} + +//___________________________________________________________________________________________________________________ +QRect Style::sliderFocusRect(const QStyleOption *option, const QWidget *widget) const +{ + const QStyleOptionSlider *sliderOption(qstyleoption_cast(option)); + + QRect r(option->rect); + + if (sliderOption->orientation == Qt::Vertical) { + int thickness = Slider_GrooveThickness + 8; + return QRect(r.center().x() - thickness / 2, r.top() + 1, thickness + 1, r.height() - 1); + } else { + int thickness = Slider_GrooveThickness + 6; + return QRect(r.left() + 1, r.center().y() - thickness / 2, r.width() - 1, thickness + 1); + } +} + +//____________________________________________________________________ +QRect Style::tabBarTabLeftButtonRect(const QStyleOption *option, const QWidget *) const +{ + // cast option and check + const QStyleOptionTabV3 *tabOptionV3(qstyleoption_cast(option)); + if (!tabOptionV3 || tabOptionV3->leftButtonSize.isEmpty()) { + return QRect(); + } + + QRect rect(option->rect); + QSize size(tabOptionV3->leftButtonSize); + QRect buttonRect(QPoint(0, 0), size); + + // vertical positioning + switch (tabOptionV3->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + buttonRect.moveLeft(rect.left() + Metrics::TabBar_TabMarginWidth); + buttonRect.moveTop((rect.height() - buttonRect.height()) / 2); + buttonRect = visualRect(option, buttonRect); + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + buttonRect.moveBottom(rect.bottom() - Metrics::TabBar_TabMarginWidth); + buttonRect.moveLeft((rect.width() - buttonRect.width()) / 2); + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + buttonRect.moveTop(rect.top() + Metrics::TabBar_TabMarginWidth); + buttonRect.moveLeft((rect.width() - buttonRect.width()) / 2); + break; + default: + break; + } + + return buttonRect; +} + +//____________________________________________________________________ +QRect Style::tabBarTabRightButtonRect(const QStyleOption *option, const QWidget *) const +{ + // cast option and check + const QStyleOptionTabV3 *tabOptionV3(qstyleoption_cast(option)); + if (!tabOptionV3 || tabOptionV3->rightButtonSize.isEmpty()) { + return QRect(); + } + + QRect rect(option->rect); + QSize size(tabOptionV3->rightButtonSize); + QRect buttonRect(QPoint(0, 0), size); + + // vertical positioning + switch (tabOptionV3->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + buttonRect.moveRight(rect.right() - Metrics::TabBar_TabMarginWidth); + buttonRect.moveTop((rect.height() - buttonRect.height()) / 2); + buttonRect = visualRect(option, buttonRect); + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + buttonRect.moveTop(rect.top() + Metrics::TabBar_TabMarginWidth); + buttonRect.moveLeft((rect.width() - buttonRect.width()) / 2); + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + buttonRect.moveBottom(rect.bottom() - Metrics::TabBar_TabMarginWidth); + buttonRect.moveLeft((rect.width() - buttonRect.width()) / 2); + break; + default: + break; + } + + return buttonRect; +} + +//____________________________________________________________________ +QRect Style::tabWidgetTabBarRect(const QStyleOption *option, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionTabWidgetFrame *tabOption = qstyleoption_cast(option); + if (!tabOption) { + return ParentStyleClass::subElementRect(SE_TabWidgetTabBar, option, widget); + } + + // do nothing if tabbar is hidden + QSize tabBarSize(tabOption->tabBarSize); + + QRect rect(option->rect); + QRect tabBarRect(QPoint(0, 0), tabBarSize); + + Qt::Alignment tabBarAlignment(styleHint(SH_TabBar_Alignment, option, widget)); + + // horizontal positioning + bool verticalTabs(isVerticalTab(tabOption->shape)); + if (verticalTabs) { + tabBarRect.setTop(option->rect.top() + 1); + tabBarRect.setBottom(option->rect.bottom() - 1); + //tabBarRect.setHeight( qMin( tabBarRect.height(), rect.height() - 2 ) ); + //if( tabBarAlignment == Qt::AlignCenter ) tabBarRect.moveTop( rect.top() + ( rect.height() - tabBarRect.height() )/2 ); + //else tabBarRect.moveTop( rect.top()+1 ); + } else { + // account for corner rects + // need to re-run visualRect to remove right-to-left handling, since it is re-added on tabBarRect at the end + QRect leftButtonRect(visualRect(option, subElementRect(SE_TabWidgetLeftCorner, option, widget))); + QRect rightButtonRect(visualRect(option, subElementRect(SE_TabWidgetRightCorner, option, widget))); + + rect.setLeft(leftButtonRect.width()); + rect.setRight(rightButtonRect.left() - 1); + + tabBarRect.moveLeft(rect.left() + 1); + tabBarRect.setWidth(rect.width() - 2); + + tabBarRect = visualRect(option, tabBarRect); + } + + // vertical positioning + switch (tabOption->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + tabBarRect.moveTop(rect.top() + 1); + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + tabBarRect.moveBottom(rect.bottom() - 1); + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + tabBarRect.moveLeft(rect.left() + 1); + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + tabBarRect.moveRight(rect.right() - 1); + break; + default: + break; + } + + return tabBarRect; +} + +//____________________________________________________________________ +QRect Style::tabWidgetTabContentsRect(const QStyleOption *option, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionTabWidgetFrame *tabOption = qstyleoption_cast(option); + if (!tabOption) { + return option->rect; + } + + // do nothing if tabbar is hidden + if (tabOption->tabBarSize.isEmpty()) { + return option->rect; + } + QRect rect = tabWidgetTabPaneRect(option, widget); + + bool documentMode(tabOption->lineWidth == 0); + if (documentMode) { + // add margin only to the relevant side + switch (tabOption->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + return rect.adjusted(0, Metrics::TabWidget_MarginWidth, 0, 0); + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + return rect.adjusted(0, 0, 0, -Metrics::TabWidget_MarginWidth); + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + return rect.adjusted(Metrics::TabWidget_MarginWidth, 0, 0, 0); + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + return rect.adjusted(0, 0, -Metrics::TabWidget_MarginWidth, 0); + default: + return rect; + } + } else { + return insideMargin(rect, Metrics::TabWidget_MarginWidth); + } +} + +//____________________________________________________________________ +QRect Style::tabWidgetTabPaneRect(const QStyleOption *option, const QWidget *) const +{ + const QStyleOptionTabWidgetFrame *tabOption = qstyleoption_cast(option); + if (!tabOption || tabOption->tabBarSize.isEmpty()) { + return option->rect; + } + + int overlap = Metrics::TabBar_BaseOverlap + 1; + QSize tabBarSize(tabOption->tabBarSize - QSize(overlap, overlap)); + + QRect rect(option->rect); + switch (tabOption->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + rect.adjust(0, tabBarSize.height(), 0, 0); + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + rect.adjust(0, 0, 0, -tabBarSize.height()); + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + rect.adjust(tabBarSize.width(), 0, 0, 0); + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + rect.adjust(0, 0, -tabBarSize.width(), 0); + break; + default: + return QRect(); + } + + return rect; +} + +//____________________________________________________________________ +QRect Style::tabWidgetCornerRect(SubElement element, const QStyleOption *option, const QWidget *) const +{ + // cast option and check + const QStyleOptionTabWidgetFrame *tabOption = qstyleoption_cast(option); + if (!tabOption) { + return option->rect; + } + + // do nothing if tabbar is hidden + QSize tabBarSize(tabOption->tabBarSize); + if (tabBarSize.isEmpty()) { + return QRect(); + } + + // do nothing for vertical tabs + bool verticalTabs(isVerticalTab(tabOption->shape)); + if (verticalTabs) { + return QRect(); + } + + QRect rect(option->rect); + QRect cornerRect; + switch (element) { + case SE_TabWidgetLeftCorner: + cornerRect = QRect(QPoint(0, 0), tabOption->leftCornerWidgetSize); + cornerRect.moveLeft(rect.left()); + break; + case SE_TabWidgetRightCorner: + cornerRect = QRect(QPoint(0, 0), tabOption->rightCornerWidgetSize); + cornerRect.moveRight(rect.right()); + break; + default: + break; + } + + // expend height to tabBarSize, if needed, to make sure base is properly rendered + cornerRect.setHeight(qMax(cornerRect.height(), tabBarSize.height() + 1)); + + switch (tabOption->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + cornerRect.moveTop(rect.top()); + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + cornerRect.moveBottom(rect.bottom()); + break; + default: + break; + } + + // return cornerRect; + cornerRect = visualRect(option, cornerRect); + return cornerRect; +} + +//____________________________________________________________________ +QRect Style::toolBoxTabContentsRect(const QStyleOption *option, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionToolBox *toolBoxOption(qstyleoption_cast(option)); + if (!toolBoxOption) { + return option->rect; + } + + // copy rect + const QRect &rect(option->rect); + + int contentsWidth(0); + if (!toolBoxOption->icon.isNull()) { + int iconSize(pixelMetric(QStyle::PM_SmallIconSize, option, widget)); + contentsWidth += iconSize; + + if (!toolBoxOption->text.isEmpty()) { + contentsWidth += Metrics::ToolBox_TabItemSpacing; + } + } + + if (!toolBoxOption->text.isEmpty()) { + int textWidth = toolBoxOption->fontMetrics.size(_mnemonics->textFlags(), toolBoxOption->text).width(); + contentsWidth += textWidth; + } + + contentsWidth += 2 * Metrics::ToolBox_TabMarginWidth; + contentsWidth = qMin(contentsWidth, rect.width()); + contentsWidth = qMax(contentsWidth, int(Metrics::ToolBox_TabMinWidth)); + return centerRect(rect, contentsWidth, rect.height()); +} + +//____________________________________________________________________ +QRect Style::genericLayoutItemRect(const QStyleOption *option, const QWidget *widget) const +{ + Q_UNUSED(widget) + return insideMargin(option->rect, -Metrics::Frame_FrameWidth); +} + +//______________________________________________________________ +QRect Style::groupBoxSubControlRect(const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const +{ + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast(option)) { + QRect rect = ParentStyleClass::subControlRect(CC_GroupBox, option, subControl, widget); + int topMargin = 0; + int topHeight = 0; + int verticalAlignment = proxy()->styleHint(SH_GroupBox_TextLabelVerticalAlignment, groupBox, widget); + + if (!groupBox->text.isEmpty()) { + topHeight = groupBox->fontMetrics.height(); + if (verticalAlignment & Qt::AlignVCenter) { + topMargin = topHeight / 2; + } else if (verticalAlignment & Qt::AlignTop) { + topMargin = topHeight; + } + } + QRect frameRect = groupBox->rect; + frameRect.setTop(topMargin); + if (subControl == SC_GroupBoxFrame) { + return rect; + } else if (subControl == SC_GroupBoxContents) { + int margin = 0; + int leftMarginExtension = 16; + return frameRect.adjusted(leftMarginExtension + margin, margin + topHeight, -margin, -margin); + } + + if (const QGroupBox *groupBoxWidget = qobject_cast(widget)) { + //Prepare metrics for a bold font + QFont font = widget->font(); + font.setBold(true); + QFontMetrics fontMetrics(font); + + QSize textRect = fontMetrics.boundingRect(groupBoxWidget->title()).size() + QSize(2, 2); + if (subControl == SC_GroupBoxCheckBox) { + int indicatorWidth = proxy()->pixelMetric(PM_IndicatorWidth, option, widget); + int indicatorHeight = proxy()->pixelMetric(PM_IndicatorHeight, option, widget); + rect.setWidth(indicatorWidth); + rect.setHeight(indicatorHeight); + rect.moveTop((textRect.height() - indicatorHeight) / 2); + } else if (subControl == SC_GroupBoxLabel) { + rect.setSize(textRect); + } + } + return rect; + } + + return ParentStyleClass::subControlRect(CC_GroupBox, option, subControl, widget); +} + +//___________________________________________________________________________________________________________________ +QRect Style::toolButtonSubControlRect(const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const +{ + + // cast option and check + const QStyleOptionToolButton *toolButtonOption = qstyleoption_cast(option); + if (!toolButtonOption) { + return ParentStyleClass::subControlRect(CC_ToolButton, option, subControl, widget); + } + + bool hasPopupMenu(toolButtonOption->features & QStyleOptionToolButton::MenuButtonPopup); + const bool hasInlineIndicator(toolButtonOption->features & QStyleOptionToolButton::HasMenu + && toolButtonOption->features & QStyleOptionToolButton::PopupDelay + && !hasPopupMenu); + + // store rect + const QRect &rect(option->rect); + int menuButtonWidth(Metrics::MenuButton_IndicatorWidth); + switch (subControl) { + case SC_ToolButtonMenu: { + // check fratures + if (!(hasPopupMenu || hasInlineIndicator)) { + return QRect(); + } + + // check features + QRect menuRect(rect); + menuRect.setLeft(rect.right() - menuButtonWidth + 1); + if (hasInlineIndicator) { + menuRect.setTop(menuRect.bottom() - menuButtonWidth + 1); + } + + return visualRect(option, menuRect); + } + + case SC_ToolButton: { + if (hasPopupMenu) { + QRect contentsRect(rect); + contentsRect.setRight(rect.right() - menuButtonWidth); + return visualRect(option, contentsRect); + } else { + return rect; + } + } + + default: + return QRect(); + } +} + +//___________________________________________________________________________________________________________________ +QRect Style::comboBoxSubControlRect(const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionComboBox *comboBoxOption(qstyleoption_cast(option)); + if (!comboBoxOption) { + return ParentStyleClass::subControlRect(CC_ComboBox, option, subControl, widget); + } + + bool editable(comboBoxOption->editable); + bool flat(editable && !comboBoxOption->frame); + + // copy rect + QRect rect(option->rect); + + switch (subControl) { + case SC_ComboBoxFrame: + return flat ? rect : QRect(); + case SC_ComboBoxListBoxPopup: + return rect; + case SC_ComboBoxArrow: { + QRect arrowRect( + rect.right() - rect.height() + 1, + rect.top(), + rect.height(), + rect.height()); + + return arrowRect; + } + + case SC_ComboBoxEditField: { + QRect labelRect; + int frameWidth(pixelMetric(PM_ComboBoxFrameWidth, option, widget)); + labelRect = QRect( + rect.left(), rect.top(), + rect.width() - rect.height() - 4, + rect.height()); + + // remove margins + if (!flat && rect.height() >= option->fontMetrics.height() + 2 * frameWidth) { + labelRect.adjust(frameWidth, frameWidth, 0, -frameWidth); + } + + return visualRect(option, labelRect); + } + + default: + break; + + } + + return ParentStyleClass::subControlRect(CC_ComboBox, option, subControl, widget); +} + +//___________________________________________________________________________________________________________________ +QRect Style::spinBoxSubControlRect(const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionSpinBox *spinBoxOption(qstyleoption_cast(option)); + if (!spinBoxOption) { + return ParentStyleClass::subControlRect(CC_SpinBox, option, subControl, widget); + } + bool flat(!spinBoxOption->frame); + + // copy rect + QRect rect(option->rect); + + switch (subControl) { + case SC_SpinBoxFrame: + return flat ? QRect() : rect; + case SC_SpinBoxUp: + if (rect.width() > 2 * rect.height() + 24) { + return QRect(rect.right() - rect.height() - 1, + rect.top(), + rect.height(), + rect.height() - 1); + } else { + return QRect(rect.right() - 0.6 * rect.height(), + rect.top(), + rect.height() * 0.6, + rect.height() / 2 + 3); + } + case SC_SpinBoxDown: { + if (rect.width() > 2 * rect.height() + 24) { + return QRect(rect.right() - 2 * rect.height(), + rect.top(), + rect.height(), + rect.height() - 1); + } else { + return QRect(rect.right() - 0.6 * rect.height(), + rect.top() + rect.height() / 2 - 2, + rect.height() * 0.6, + rect.height() / 2 + 1); + } + } + + case SC_SpinBoxEditField: { + int frameWidth(pixelMetric(PM_SpinBoxFrameWidth, option, widget)); + + QRect labelRect; + + if (rect.width() > 2 * rect.height() + 24) { + labelRect = QRect(rect.left(), rect.top(), + rect.width() - 2 * rect.height() - frameWidth, + rect.height()); + } else { + labelRect = QRect(rect.left(), rect.top(), + rect.width() - 0.6 * rect.height() - frameWidth, + rect.height()); + } + + // remove right side line editor margins + if (!flat && labelRect.height() >= option->fontMetrics.height() + 2 * frameWidth) { + labelRect.adjust(frameWidth, frameWidth, 0, -frameWidth); + } + + return visualRect(option, labelRect); + } + + default: + break; + } + + return ParentStyleClass::subControlRect(CC_SpinBox, option, subControl, widget); +} + +//___________________________________________________________________________________________________________________ +QRect Style::scrollBarInternalSubControlRect(const QStyleOptionComplex *option, SubControl subControl) const +{ + const QRect &rect = option->rect; + const State &state(option->state); + bool horizontal(state & State_Horizontal); + + switch (subControl) { + case SC_ScrollBarSubLine: { + int majorSize(scrollBarButtonHeight(_subLineButtons)); + if (horizontal) { + return visualRect(option, QRect(rect.left(), rect.top(), majorSize, rect.height())); + } else { + return visualRect(option, QRect(rect.left(), rect.top(), rect.width(), majorSize)); + } + } + case SC_ScrollBarAddLine: { + int majorSize(scrollBarButtonHeight(_addLineButtons)); + if (horizontal) { + return visualRect(option, QRect(rect.right() - majorSize + 1, rect.top(), majorSize, rect.height())); + } else { + return visualRect(option, QRect(rect.left(), rect.bottom() - majorSize + 1, rect.width(), majorSize)); + } + } + + default: + return QRect(); + } +} + +//___________________________________________________________________________________________________________________ +QRect Style::scrollBarSubControlRect(const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionSlider *sliderOption(qstyleoption_cast(option)); + if (!sliderOption) { + return ParentStyleClass::subControlRect(CC_ScrollBar, option, subControl, widget); + } + + // get relevant state + const State &state(option->state); + bool horizontal(state & State_Horizontal); + + switch (subControl) { + case SC_ScrollBarSubLine: + case SC_ScrollBarAddLine: + return QRect(); + case SC_ScrollBarGroove: { + QRect topRect = visualRect(option, scrollBarInternalSubControlRect(option, SC_ScrollBarSubLine)); + QRect bottomRect = visualRect(option, scrollBarInternalSubControlRect(option, SC_ScrollBarAddLine)); + + QPoint topLeftCorner; + QPoint botRightCorner; + + if (horizontal) { + topLeftCorner = QPoint(topRect.right() + 1, topRect.top()); + botRightCorner = QPoint(bottomRect.left() - 1, topRect.bottom()); + } else { + topLeftCorner = QPoint(topRect.left(), topRect.bottom() + 1); + botRightCorner = QPoint(topRect.right(), bottomRect.top() - 1); + } + + // define rect + return visualRect(option, QRect(topLeftCorner, botRightCorner)); + } + case SC_ScrollBarSlider: { + // handle RTL here to unreflect things if need be + QRect groove = visualRect(option, subControlRect(CC_ScrollBar, option, SC_ScrollBarGroove, widget)); + groove.adjust(0, 0, 1, 1); + + if (sliderOption->minimum == sliderOption->maximum) { + return groove; + } + + // Figure out how much room there is + int space(horizontal ? groove.width() : groove.height()); + + // Calculate the portion of this space that the slider should occupy + int sliderSize = space * qreal(sliderOption->pageStep) / (sliderOption->maximum - sliderOption->minimum + sliderOption->pageStep); + sliderSize = qMax(sliderSize, static_cast(Metrics::ScrollBar_MinSliderHeight)); + sliderSize = qMin(sliderSize, space); + + space -= sliderSize; + if (space <= 0) { + return groove; + } + + int pos = qRound(qreal(sliderOption->sliderPosition - sliderOption->minimum) / (sliderOption->maximum - sliderOption->minimum) * space); + if (sliderOption->upsideDown) { + pos = space - pos; + } + if (horizontal) { + return visualRect(option, QRect(groove.left() + pos, groove.top(), sliderSize, groove.height())); + } else { + return visualRect(option, QRect(groove.left(), groove.top() + pos, groove.width(), sliderSize)); + } + } + case SC_ScrollBarSubPage: { + // handle RTL here to unreflect things if need be + QRect slider = visualRect(option, subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget)); + QRect groove = visualRect(option, subControlRect(CC_ScrollBar, option, SC_ScrollBarGroove, widget)); + + if (horizontal) { + return visualRect(option, QRect(groove.left(), groove.top(), slider.left() - groove.left(), groove.height())); + } else { + return visualRect(option, QRect(groove.left(), groove.top(), groove.width(), slider.top() - groove.top())); + } + } + case SC_ScrollBarAddPage: { + // handle RTL here to unreflect things if need be + QRect slider = visualRect(option, subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget)); + QRect groove = visualRect(option, subControlRect(CC_ScrollBar, option, SC_ScrollBarGroove, widget)); + + if (horizontal) { + return visualRect(option, QRect(slider.right() + 1, groove.top(), groove.right() - slider.right(), groove.height())); + } else { + return visualRect(option, QRect(groove.left(), slider.bottom() + 1, groove.width(), groove.bottom() - slider.bottom())); + } + } + + default: + return ParentStyleClass::subControlRect(CC_ScrollBar, option, subControl, widget); + } +} + +//___________________________________________________________________________________________________________________ +QRect Style::dialSubControlRect(const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionSlider *sliderOption(qstyleoption_cast(option)); + if (!sliderOption) { + return ParentStyleClass::subControlRect(CC_Dial, option, subControl, widget); + } + + // adjust rect to be square, and centered + QRect rect(option->rect); + int dimension(qMin(rect.width(), rect.height())); + rect = centerRect(rect, dimension, dimension); + + switch (subControl) { + case QStyle::SC_DialGroove: + return insideMargin(rect, (Metrics::Slider_ControlThickness - Metrics::Slider_GrooveThickness) / 2 + 2); + case QStyle::SC_DialHandle: { + + // calculate angle at which handle needs to be drawn + qreal angle(dialAngle(sliderOption, sliderOption->sliderPosition)); + + // groove rect + QRectF grooveRect(insideMargin(rect, Metrics::Slider_ControlThickness / 2)); + qreal radius(grooveRect.width() / 2); + + // slider center + QPointF center(grooveRect.center() + QPointF(radius * std::cos(angle), -radius * std::sin(angle))); + + // slider rect + QRect handleRect(0, 0, Metrics::Slider_ControlThickness, Metrics::Slider_ControlThickness); + handleRect.moveCenter(center.toPoint()); + return handleRect; + } + + default: + return ParentStyleClass::subControlRect(CC_Dial, option, subControl, widget); + + } +} + +//___________________________________________________________________________________________________________________ +QRect Style::sliderSubControlRect(const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionSlider *sliderOption(qstyleoption_cast(option)); + if (!sliderOption) { + return ParentStyleClass::subControlRect(CC_Slider, option, subControl, widget); + } + + switch (subControl) { + case SC_SliderGroove: { + // direction + bool horizontal(sliderOption->orientation == Qt::Horizontal); + + // get base class rect + QRect grooveRect(ParentStyleClass::subControlRect(CC_Slider, option, subControl, widget)); + grooveRect = insideMargin(grooveRect, pixelMetric(PM_DefaultFrameWidth, option, widget)); + + // centering + if (horizontal) { + grooveRect = centerRect(grooveRect, grooveRect.width(), Metrics::Slider_GrooveThickness); + } else { + grooveRect = centerRect(grooveRect, Metrics::Slider_GrooveThickness, grooveRect.height()); + } + return grooveRect; + } + + default: + return ParentStyleClass::subControlRect(CC_Slider, option, subControl, widget); + } +} + +//______________________________________________________________ +QSize Style::checkBoxSizeFromContents(const QStyleOption *, const QSize &contentsSize, const QWidget *) const +{ + // get contents size + QSize size(contentsSize); + + // add focus height + size = expandSize(size, 0, Metrics::CheckBox_FocusMarginWidth); + + // make sure there is enough height for indicator + size.setHeight(qMax(size.height(), int(Metrics::CheckBox_Size))); + + // Add space for the indicator and the icon + size.rwidth() += Metrics::CheckBox_Size + Metrics::CheckBox_ItemSpacing; + + // also add extra space, to leave room to the right of the label + size.rwidth() += Metrics::CheckBox_ItemSpacing; + + return size; +} + +//______________________________________________________________ +QSize Style::lineEditSizeFromContents(const QStyleOption *option, const QSize &contentsSize, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionFrame *frameOption(qstyleoption_cast(option)); + if (!frameOption) { + return contentsSize; + } + + bool flat(frameOption->lineWidth == 0); + int frameWidth(pixelMetric(PM_DefaultFrameWidth, option, widget)); + QSize size = flat ? contentsSize : expandSize(contentsSize, frameWidth); + + size.setHeight(qMax(size.height(), int(Metrics::LineEdit_MinHeight))); + size.setWidth(qMax(size.width(), int(Metrics::LineEdit_MinWidth))); + + return size; +} + +//______________________________________________________________ +QSize Style::comboBoxSizeFromContents(const QStyleOption *option, const QSize &contentsSize, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionComboBox *comboBoxOption(qstyleoption_cast(option)); + if (!comboBoxOption) { + return contentsSize; + } + + // copy size + QSize size(contentsSize); + + // add relevant margin + bool flat(!comboBoxOption->frame); + int frameWidth(pixelMetric(PM_ComboBoxFrameWidth, option, widget)); + if (!flat) { + size = expandSize(size, frameWidth); + } + + size.rwidth() += Metrics::MenuButton_IndicatorWidth; + size.rwidth() += Metrics::Button_ItemSpacing; + + // FIXME this shouldn't be needed but apparently some width is still missing + size.rwidth() += size.height(); + + // make sure there is enough height for the button + size.setHeight(qMax(size.height(), int(Metrics::MenuButton_IndicatorWidth))); + + size = expandSize(size, Metrics::ComboBox_MarginWidth, Metrics::ComboBox_MarginHeight); + + // set minimum size + size.setHeight(qMax(size.height(), int(Metrics::ComboBox_MinHeight))); + size.setWidth(qMax(size.width(), int(Metrics::ComboBox_MinWidth))); + + return size; +} + +//______________________________________________________________ +QSize Style::spinBoxSizeFromContents(const QStyleOption *option, const QSize &contentsSize, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionSpinBox *spinBoxOption(qstyleoption_cast(option)); + if (!spinBoxOption) { + return contentsSize; + } + + bool flat(!spinBoxOption->frame); + + // copy size + QSize size(contentsSize); + + // add editor margins + int frameWidth(pixelMetric(PM_SpinBoxFrameWidth, option, widget)); + if (!flat) { + size = expandSize(size, frameWidth); + } + + size.rwidth() += 2 * Metrics::SpinBox_MinHeight; + size.rwidth() += Metrics::Button_ItemSpacing; + + // FIXME this shouldn't be needed but apparently some width is still missing + size.rwidth() += size.height() / 2; + + // set minimum size + size.setHeight(qMax(size.height(), int(Metrics::SpinBox_MinHeight))); + size.setWidth(qMax(size.width(), int(Metrics::SpinBox_MinWidth))); + + return size; +} + +//______________________________________________________________ +QSize Style::sliderSizeFromContents(const QStyleOption *option, const QSize &contentsSize, const QWidget *) const +{ + // cast option and check + const QStyleOptionSlider *sliderOption(qstyleoption_cast(option)); + if (!sliderOption) { + return contentsSize; + } + + // store tick position and orientation + const QSlider::TickPosition &tickPosition(sliderOption->tickPosition); + bool horizontal(sliderOption->orientation == Qt::Horizontal); + bool disableTicks(!Adwaita::Config::SliderDrawTickMarks); + + // do nothing if no ticks are requested + if (tickPosition == QSlider::NoTicks) { + return contentsSize; + } + + /* + * Qt adds its own tick length directly inside QSlider. + * Take it out and replace by ours, if needed + */ + const int tickLength(disableTicks ? 0 : ( + Metrics::Slider_TickLength + Metrics::Slider_TickMarginWidth + + (Metrics::Slider_GrooveThickness - Metrics::Slider_ControlThickness) / 2)); + + int builtInTickLength(5); + + QSize size(contentsSize); + if (horizontal) { + if (tickPosition & QSlider::TicksAbove) { + size.rheight() += tickLength - builtInTickLength; + } + if (tickPosition & QSlider::TicksBelow) { + size.rheight() += tickLength - builtInTickLength; + } + } else { + if (tickPosition & QSlider::TicksAbove) { + size.rwidth() += tickLength - builtInTickLength; + } + if (tickPosition & QSlider::TicksBelow) { + size.rwidth() += tickLength - builtInTickLength; + } + } + + return size; +} + +//______________________________________________________________ +QSize Style::pushButtonSizeFromContents(const QStyleOption *option, const QSize &contentsSize, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionButton *buttonOption(qstyleoption_cast(option)); + if (!buttonOption) { + return contentsSize; + } + + // output + QSize size; + + // check text and icon + bool hasText(!buttonOption->text.isEmpty()); + bool flat(buttonOption->features & QStyleOptionButton::Flat); + bool hasIcon(!buttonOption->icon.isNull()); + + if (!(hasText || hasIcon)) { + /* + no text nor icon is passed. + assume custom button and use contentsSize as a starting point + */ + size = contentsSize; + } else { + /* + rather than trying to guess what Qt puts into its contents size calculation, + we recompute the button size entirely, based on button option + this ensures consistency with the rendering stage + */ + + // update has icon to honour showIconsOnPushButtons, when possible + hasIcon &= (showIconsOnPushButtons() || flat || !hasText); + + // text + if (hasText) { + size = buttonOption->fontMetrics.size(Qt::TextShowMnemonic, buttonOption->text); + } + + // icon + if (hasIcon) { + QSize iconSize = buttonOption->iconSize; + if (!iconSize.isValid()) { + iconSize = QSize(pixelMetric(PM_SmallIconSize, option, widget), pixelMetric(PM_SmallIconSize, option, widget)); + } + + size.setHeight(qMax(size.height(), iconSize.height())); + size.rwidth() += iconSize.width(); + + if (hasText) { + size.rwidth() += Metrics::Button_ItemSpacing; + } + } + + } + + // menu + bool hasMenu(buttonOption->features & QStyleOptionButton::HasMenu); + if (hasMenu) { + size.rwidth() += Metrics::MenuButton_IndicatorWidth; + if (hasText || hasIcon) { + size.rwidth() += Metrics::Button_ItemSpacing; + } + } + + // expand with buttons margin + size = expandSize(size, Metrics::Button_MarginWidth, Button_MarginHeight); + + // finally add frame margins + size = expandSize(size, Metrics::Frame_FrameWidth); + + // make sure buttons have a minimum width + if (hasText) { + size.setWidth(qMax(size.width(), int(Metrics::Button_MinWidth))); + } + + // make sure buttons have a minimum height + size.setHeight(qMax(size.height(), int(Metrics::Button_MinHeight))); + + return size; +} + +//______________________________________________________________ +QSize Style::toolButtonSizeFromContents(const QStyleOption *option, const QSize &contentsSize, const QWidget *) const +{ + // cast option and check + const QStyleOptionToolButton *toolButtonOption = qstyleoption_cast(option); + if (!toolButtonOption) { + return contentsSize; + } + + // copy size + QSize size = contentsSize; + + // get relevant state flags + const State &state(option->state); + bool autoRaise(state & State_AutoRaise); + bool hasPopupMenu(toolButtonOption->features & QStyleOptionToolButton::MenuButtonPopup); + const bool hasInlineIndicator(toolButtonOption->features & QStyleOptionToolButton::HasMenu + && toolButtonOption->features & QStyleOptionToolButton::PopupDelay + && !hasPopupMenu); + + int marginWidth(Metrics::ToolButton_MarginWidth); + + if (hasInlineIndicator) { + size.rwidth() += Metrics::ToolButton_InlineIndicatorWidth; + } + size = expandSize(size, marginWidth); + + // We need to add 1px as the toolbutton is smaller by 1px when rendering button frame + size = expandSize(size, 1); + + return size; +} + +//______________________________________________________________ +QSize Style::menuBarItemSizeFromContents(const QStyleOption *, const QSize &contentsSize, const QWidget *) const +{ + return expandSize(contentsSize, Metrics::MenuBarItem_MarginWidth, Metrics::MenuBarItem_MarginHeight); +} + +//______________________________________________________________ +QSize Style::menuItemSizeFromContents(const QStyleOption *option, const QSize &contentsSize, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionMenuItem *menuItemOption = qstyleoption_cast(option); + if (!menuItemOption) { + return contentsSize; + } + + /* + * First calculate the intrinsic size of the item. + * this must be kept consistent with what's in drawMenuItemControl + */ + QSize size(contentsSize); + switch (menuItemOption->menuItemType) { + case QStyleOptionMenuItem::Normal: + case QStyleOptionMenuItem::DefaultItem: + case QStyleOptionMenuItem::SubMenu: { + int iconWidth = 0; + if (showIconsInMenuItems()) { + iconWidth = isQtQuickControl(option, widget) ? qMax(pixelMetric(PM_SmallIconSize, option, widget), menuItemOption->maxIconWidth) : menuItemOption->maxIconWidth; + } + int leftColumnWidth(iconWidth); + + // add space with respect to text + leftColumnWidth += Metrics::MenuItem_ItemSpacing; + + // add checkbox indicator width + if (menuItemOption->menuHasCheckableItems) { + leftColumnWidth += Metrics::CheckBox_Size + Metrics::MenuItem_ItemSpacing; + } + + // add spacing for accelerator + /* + * Note: + * The width of the accelerator itself is not included here since + * Qt will add that on separately after obtaining the + * sizeFromContents() for each menu item in the menu to be shown + * ( see QMenuPrivate::calcActionRects() ) + */ + bool hasAccelerator(menuItemOption->text.indexOf(QLatin1Char('\t')) >= 0); + if (hasAccelerator) { + size.rwidth() += Metrics::MenuItem_AcceleratorSpace; + } + + // right column + int rightColumnWidth = Metrics::MenuButton_IndicatorWidth + Metrics::MenuItem_ItemSpacing; + size.rwidth() += leftColumnWidth + rightColumnWidth; + + // make sure height is large enough for icon and arrow + size.setHeight(qMax(size.height(), int(Metrics::MenuButton_IndicatorWidth))); + size.setHeight(qMax(size.height(), int(Metrics::CheckBox_Size))); + size.setHeight(qMax(size.height(), iconWidth)); + + // Looks Gtk adds some additional space to the right + size.rwidth() += Metrics::MenuItem_MarginWidth * 4; + + return expandSize(size, Metrics::MenuItem_MarginWidth); + } + + case QStyleOptionMenuItem::Separator: { + if (menuItemOption->text.isEmpty() && menuItemOption->icon.isNull()) { + return expandSize(QSize(0, 1), Metrics::MenuItem_MarginWidth, 0); + } else { + // build toolbutton option + QStyleOptionToolButton toolButtonOption(separatorMenuItemOption(menuItemOption, widget)); + + // make sure height is large enough for icon and text + int iconWidth(menuItemOption->maxIconWidth); + int textHeight(menuItemOption->fontMetrics.height()); + if (!menuItemOption->icon.isNull()) { + size.setHeight(qMax(size.height(), iconWidth)); + } + if (!menuItemOption->text.isEmpty()) { + size.setHeight(qMax(size.height(), textHeight)); + size.setWidth(qMax(size.width(), menuItemOption->fontMetrics.width(menuItemOption->text))); + } + + return sizeFromContents(CT_ToolButton, &toolButtonOption, size, widget); + } + } + + // for all other cases, return input + default: + return contentsSize; + } +} + +//______________________________________________________________ +QSize Style::progressBarSizeFromContents(const QStyleOption *option, const QSize &contentsSize, const QWidget *) const +{ + // cast option + const QStyleOptionProgressBar *progressBarOption(qstyleoption_cast(option)); + if (!progressBarOption) { + return contentsSize; + } + + const QStyleOptionProgressBarV2 *progressBarOption2(qstyleoption_cast(option)); + bool horizontal(!progressBarOption2 || progressBarOption2->orientation == Qt::Horizontal); + + // make local copy + QSize size(contentsSize); + + if (horizontal) { + // check text visibility + bool textVisible(progressBarOption->textVisible); + + size.setWidth(qMax(size.width(), int(Metrics::ProgressBar_Thickness))); + size.setHeight(qMax(size.height(), int(Metrics::ProgressBar_Thickness))); + if (textVisible) { + size.setHeight(qMax(size.height(), option->fontMetrics.height())); + } + } else { + size.setHeight(qMax(size.height(), int(Metrics::ProgressBar_Thickness))); + size.setWidth(qMax(size.width(), int(Metrics::ProgressBar_Thickness))); + } + + return size; +} + +//______________________________________________________________ +QSize Style::tabWidgetSizeFromContents(const QStyleOption *option, const QSize &contentsSize, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionTabWidgetFrame *tabOption = qstyleoption_cast(option); + if (!tabOption) { + return expandSize(contentsSize, Metrics::TabWidget_MarginWidth); + } + + // try find direct children of type QTabBar and QStackedWidget + // this is needed in order to add TabWidget margins only if they are necessary around tabWidget content, not the tabbar + if (!widget) { + return expandSize(contentsSize, Metrics::TabWidget_MarginWidth); + } + + QTabBar *tabBar = nullptr; + QStackedWidget *stack = nullptr; + auto children(widget->children()); + foreach (auto child, children) { + if (!tabBar) { + tabBar = qobject_cast(child); + } + if (!stack) { + stack = qobject_cast(child); + } + if (tabBar && stack) { + break; + } + } + + if (!(tabBar && stack)) { + return expandSize(contentsSize, Metrics::TabWidget_MarginWidth); + } + + // tab orientation + bool verticalTabs(tabOption && isVerticalTab(tabOption->shape)); + if (verticalTabs) { + int tabBarHeight = tabBar->minimumSizeHint().height(); + int stackHeight = stack->minimumSizeHint().height(); + if (contentsSize.height() == tabBarHeight && tabBarHeight + 2 * (Metrics::Frame_FrameWidth - 1) >= stackHeight + 2 * Metrics::TabWidget_MarginWidth) { + return QSize(contentsSize.width() + 2 * Metrics::TabWidget_MarginWidth, contentsSize.height() + 2 * (Metrics::Frame_FrameWidth - 1)); + } else { + return expandSize(contentsSize, Metrics::TabWidget_MarginWidth); + } + } else { + int tabBarWidth = tabBar->minimumSizeHint().width(); + int stackWidth = stack->minimumSizeHint().width(); + if (contentsSize.width() == tabBarWidth && tabBarWidth + 2 * (Metrics::Frame_FrameWidth - 1) >= stackWidth + 2 * Metrics::TabWidget_MarginWidth) { + return QSize(contentsSize.width() + 2 * (Metrics::Frame_FrameWidth - 1), contentsSize.height() + 2 * Metrics::TabWidget_MarginWidth); + } else { + return expandSize(contentsSize, Metrics::TabWidget_MarginWidth); + } + } +} + +//______________________________________________________________ +QSize Style::tabBarTabSizeFromContents(const QStyleOption *option, const QSize &contentsSize, const QWidget *) const +{ + const QStyleOptionTab *tabOption(qstyleoption_cast(option)); + const QStyleOptionTabV3 *tabOptionV3(qstyleoption_cast(option)); + bool hasText(tabOption && !tabOption->text.isEmpty()); + bool hasIcon(tabOption && !tabOption->icon.isNull()); + bool hasLeftButton(tabOptionV3 && !tabOptionV3->leftButtonSize.isEmpty()); + bool hasRightButton(tabOptionV3 && !tabOptionV3->leftButtonSize.isEmpty()); + + // calculate width increment for horizontal tabs + int widthIncrement = 0; + if (hasIcon && !(hasText || hasLeftButton || hasRightButton)) { + widthIncrement -= 4; + } + if (hasText && hasIcon) { + widthIncrement += Metrics::TabBar_TabItemSpacing; + } + if (hasLeftButton && (hasText || hasIcon)) { + widthIncrement += Metrics::TabBar_TabItemSpacing; + } + if (hasRightButton && (hasText || hasIcon || hasLeftButton)) { + widthIncrement += Metrics::TabBar_TabItemSpacing; + } + + // add margins + QSize size(contentsSize); + + if (hasText) { + widthIncrement += option->fontMetrics.width(tabOption->text) * 0.2; + } + + // compare to minimum size + bool verticalTabs(tabOption && isVerticalTab(tabOption)); + if (verticalTabs) { + size.rheight() += widthIncrement; + if (hasIcon && !hasText) { + size = size.expandedTo(QSize(Metrics::TabBar_TabMinHeight, 0)); + } else { + size = size.expandedTo(QSize(Metrics::TabBar_TabMinHeight, Metrics::TabBar_TabMinWidth)); + } + } else { + size.rwidth() += widthIncrement; + if (hasIcon && !hasText) { + size = size.expandedTo(QSize(0, Metrics::TabBar_TabMinHeight)); + } else { + size = size.expandedTo(QSize(Metrics::TabBar_TabMinWidth, Metrics::TabBar_TabMinHeight)); + } + } + + return size; +} + +//______________________________________________________________ +QSize Style::headerSectionSizeFromContents(const QStyleOption *option, const QSize &contentsSize, const QWidget *) const +{ + // cast option and check + const QStyleOptionHeader *headerOption(qstyleoption_cast(option)); + if (!headerOption) { + return contentsSize; + } + + // get text size + bool horizontal(headerOption->orientation == Qt::Horizontal); + bool hasText(!headerOption->text.isEmpty()); + bool hasIcon(!headerOption->icon.isNull()); + + QSize textSize(hasText ? headerOption->fontMetrics.size(0, headerOption->text) : QSize()); + QSize iconSize(hasIcon ? QSize(22, 22) : QSize()); + + // contents width + int contentsWidth(0); + if (hasText) { + contentsWidth += textSize.width(); + } + if (hasIcon) { + contentsWidth += iconSize.width(); + if (hasText) { + contentsWidth += Metrics::Header_ItemSpacing; + } + } + + // contents height + int contentsHeight(headerOption->fontMetrics.height()); + if (hasIcon) { + contentsHeight = qMax(contentsHeight, iconSize.height()); + } + + if (horizontal) { + // also add space for icon + contentsWidth += Metrics::Header_ArrowSize + Metrics::Header_ItemSpacing; + contentsHeight = qMax(contentsHeight, int(Metrics::Header_ArrowSize)); + } + + // update contents size, add margins and return + QSize size(contentsSize.expandedTo(QSize(contentsWidth, contentsHeight))); + return expandSize(size, Metrics::Header_MarginWidth); +} + +//______________________________________________________________ +QSize Style::itemViewItemSizeFromContents(const QStyleOption *option, const QSize &contentsSize, const QWidget *widget) const +{ + // call base class + QSize size(ParentStyleClass::sizeFromContents(CT_ItemViewItem, option, contentsSize, widget)); + return expandSize(size, Metrics::ItemView_ItemMarginWidth); +} + +//______________________________________________________________ +bool Style::drawFramePrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // copy palette and rect + const QPalette &palette(option->palette); + const QRect &rect(option->rect); + + // detect title widgets + const bool isTitleWidget(Adwaita::Config::TitleWidgetDrawFrame && widget && widget->parent() && widget->parent()->inherits("KTitleWidget")); + // copy state + const State &state(option->state); + if (!isTitleWidget && !(state & (State_Sunken | State_Raised))) { + return true; + } + + const bool isInputWidget((widget && widget->testAttribute(Qt::WA_Hover)) + || (isQtQuickControl(option, widget) && option->styleObject->property("elementType").toString() == QStringLiteral("edit"))); + bool enabled(state & State_Enabled); + bool mouseOver((state & State_Active) && enabled && isInputWidget && (state & State_MouseOver)); + bool hasFocus(enabled && isInputWidget && (state & State_HasFocus)); + + // focus takes precedence over mouse over + _animations->inputWidgetEngine().updateState(widget, AnimationFocus, hasFocus); + _animations->inputWidgetEngine().updateState(widget, AnimationHover, mouseOver && !hasFocus); + + // retrieve animation mode and opacity + AnimationMode mode(_animations->inputWidgetEngine().frameAnimationMode(widget)); + qreal opacity(_animations->inputWidgetEngine().frameOpacity(widget)); + + // Style options + StyleOptions styleOptions(palette); + styleOptions.setHasFocus(hasFocus); + styleOptions.setMouseOver(mouseOver); + styleOptions.setOpacity(opacity); + styleOptions.setAnimationMode(mode); + + // render + if (!Adwaita::Config::SidePanelDrawFrame && widget && widget->property(PropertyNames::sidePanelView).toBool()) { + QColor outline(Colors::sidePanelOutlineColor(styleOptions)); + bool reverseLayout(option->direction == Qt::RightToLeft); + Side side(reverseLayout ? SideRight : SideLeft); + _helper->renderSidePanelFrame(painter, rect, outline, side); + } else if (qobject_cast(widget)) { + QColor outline(Colors::frameOutlineColor(styleOptions)); + _helper->renderSquareFrame(painter, rect, outline, hasFocus); + } else { + QColor background(isTitleWidget ? palette.color(widget->backgroundRole()) : QColor()); + QColor outline(Colors::frameOutlineColor(styleOptions)); + _helper->renderFrame(painter, rect, background, outline, hasFocus); + } + + return true; +} + +//______________________________________________________________ +bool Style::drawFrameLineEditPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // copy palette and rect + const QPalette &palette(option->palette); + const QRect &rect(option->rect); + + // make sure there is enough room to render frame + if (rect.height() < 2 * Metrics::LineEdit_FrameWidth + option->fontMetrics.height()) { + QColor background(palette.currentColorGroup() == QPalette::Disabled ? palette.color(QPalette::Window) : palette.color(QPalette::Base)); + + painter->setPen(Qt::NoPen); + painter->setBrush(background); + painter->drawRect(rect); + return true; + } else { + // copy state + const State &state(option->state); + bool enabled(state & State_Enabled); + bool mouseOver((state & State_Active) && enabled && (state & State_MouseOver)); + bool hasFocus(enabled && (state & State_HasFocus)); + + // focus takes precedence over mouse over + _animations->inputWidgetEngine().updateState(widget, AnimationFocus, hasFocus); + + // retrieve animation mode and opacity + AnimationMode mode(_animations->inputWidgetEngine().frameAnimationMode(widget)); + qreal opacity(_animations->inputWidgetEngine().frameOpacity(widget)); + + // Style options + StyleOptions styleOptions(palette); + styleOptions.setMouseOver(mouseOver); + styleOptions.setHasFocus(hasFocus); + styleOptions.setOpacity(opacity); + styleOptions.setAnimationMode(mode); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + // render + QColor background(palette.currentColorGroup() == QPalette::Disabled ? palette.color(QPalette::Window) : palette.color(QPalette::Base)); + QColor outline(Colors::inputOutlineColor(styleOptions)); + if (qobject_cast(widget)) { + _helper->renderFlatFrame(painter, rect, background, outline, hasFocus); + } else { + _helper->renderFrame(painter, rect, background, outline, hasFocus); + } + } + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawFrameFocusRectPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + if (option->styleObject && option->styleObject->property("elementType") == QLatin1String("button")) { + return true; + } + + const State &state(option->state); + QRectF rect(QRectF(option->rect).adjusted(0, 0, -1, -1)); + const QPalette &palette(option->palette); + + if (rect.width() < 10) { + return true; + } + + QColor outlineColor(Colors::mix(palette.color(QPalette::Window), palette.color(QPalette::WindowText), 0.35)); + QPen pen(outlineColor, 1); + pen.setStyle(Qt::CustomDashLine); + pen.setDashPattern(QVector() << 2 << 1); + + painter->setRenderHint(QPainter::Antialiasing, false); + + painter->setPen(pen); + painter->drawRoundedRect(rect, 2, 2); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawFrameMenuPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // only draw frame for (expanded) toolbars and QtQuick controls + // do nothing for other cases, for which frame is rendered via drawPanelMenuPrimitive + if (qobject_cast(widget)) { + const QPalette &palette(option->palette); + QColor background(Colors::frameBackgroundColor(StyleOptions(palette))); + QColor outline(Colors::frameOutlineColor(StyleOptions(palette))); + + bool hasAlpha(_helper->hasAlphaChannel(widget)); + _helper->renderMenuFrame(painter, option->rect, background, outline, hasAlpha); + } else if (isQtQuickControl(option, widget)) { + const QPalette &palette(option->palette); + QColor background(Colors::frameBackgroundColor(StyleOptions(palette))); + QColor outline(Colors::frameOutlineColor(StyleOptions(palette))); + + bool hasAlpha(_helper->hasAlphaChannel(widget)); + _helper->renderMenuFrame(painter, option->rect, background, outline, hasAlpha); + } + + return true; +} + +//______________________________________________________________ +bool Style::drawFrameGroupBoxPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *) const +{ + return true; +} + +//___________________________________________________________________________________ +bool Style::drawFrameTabWidgetPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionTabWidgetFrameV2 *tabOption(qstyleoption_cast(option)); + if (!tabOption) { + return true; + } + + // do nothing if tabbar is hidden + bool isQtQuickControl(this->isQtQuickControl(option, widget)); + if (tabOption->tabBarSize.isEmpty() && !isQtQuickControl) { + return true; + } + + // adjust rect to handle overlaps + QRect rect(option->rect); + + QRect tabBarRect(tabOption->tabBarRect); + QSize tabBarSize(tabOption->tabBarSize); + Corners corners = AllCorners; + + // adjust corners to deal with oversized tabbars + switch (tabOption->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + if (isQtQuickControl) { + rect.adjust(-1, -1, 1, 0); + } + if (tabBarSize.width() >= rect.width() - 2 * Metrics::Frame_FrameRadius) { + corners &= ~CornersTop; + } + if (tabBarRect.left() < rect.left() + Metrics::Frame_FrameRadius) { + corners &= ~CornerTopLeft; + } + if (tabBarRect.right() > rect.right() - Metrics::Frame_FrameRadius) { + corners &= ~CornerTopRight; + } + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + if (isQtQuickControl) { + rect.adjust(-1, 0, 1, 1); + } + if (tabBarSize.width() >= rect.width() - 2 * Metrics::Frame_FrameRadius) { + corners &= ~CornersBottom; + } + if (tabBarRect.left() < rect.left() + Metrics::Frame_FrameRadius) { + corners &= ~CornerBottomLeft; + } + if (tabBarRect.right() > rect.right() - Metrics::Frame_FrameRadius) { + corners &= ~CornerBottomRight; + } + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + if (isQtQuickControl) { + rect.adjust(-1, 0, 0, 0); + } + if (tabBarSize.height() >= rect.height() - 2 * Metrics::Frame_FrameRadius) { + corners &= ~CornersLeft; + } + if (tabBarRect.top() < rect.top() + Metrics::Frame_FrameRadius) { + corners &= ~CornerTopLeft; + } + if (tabBarRect.bottom() > rect.bottom() - Metrics::Frame_FrameRadius) { + corners &= ~CornerBottomLeft; + } + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + if (isQtQuickControl) { + rect.adjust(0, 0, 1, 0); + } + if (tabBarSize.height() >= rect.height() - 2 * Metrics::Frame_FrameRadius) { + corners &= ~CornersRight; + } + if (tabBarRect.top() < rect.top() + Metrics::Frame_FrameRadius) { + corners &= ~CornerTopRight; + } + if (tabBarRect.bottom() > rect.bottom() - Metrics::Frame_FrameRadius) { + corners &= ~CornerBottomRight; + } + break; + default: + break; + } + + // define colors + const QPalette &palette(option->palette); + QColor background(palette.color(QPalette::Base)); + QColor outline(Colors::frameOutlineColor(StyleOptions(palette))); + _helper->renderTabWidgetFrame(painter, rect, background, outline, corners); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawFrameTabBarBasePrimitive(const QStyleOption *option, QPainter *painter, const QWidget *) const +{ + // tabbar frame used either for 'separate' tabbar, or in 'document mode' + + // cast option and check + const QStyleOptionTabBarBase *tabOption(qstyleoption_cast(option)); + if (!tabOption) { + return true; + } + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setState(option->state); + + // get rect, orientation, palette + QRect rect(option->rect); + QColor outline(Colors::frameOutlineColor(styleOptions)); + QColor background = Colors::tabBarColor(styleOptions); + + // setup painter + painter->setBrush(background); + painter->setRenderHint(QPainter::Antialiasing, false); + painter->setPen(QPen(outline, 1)); + + painter->drawRect(rect.adjusted(0, 0, -1, -1)); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawFrameWindowPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *) const +{ + // copy rect and palette + const QRect &rect(option->rect); + const QPalette &palette(option->palette); + State state(option->state); + bool selected(state & State_Selected); + + // Style options + StyleOptions styleOptions(palette); + styleOptions.setMouseOver(false); + styleOptions.setHasFocus(selected); + + // render frame outline + QColor outline(Colors::frameOutlineColor(styleOptions)); + _helper->renderMenuFrame(painter, rect, QColor(), outline); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawIndicatorArrowPrimitive(ArrowOrientation orientation, const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // store rect and palette + const QRect &rect(option->rect); + const QPalette &palette(option->palette); + + // store state + const State &state(option->state); + bool enabled(state & State_Enabled); + bool mouseOver((state & State_Active) && enabled && (state & State_MouseOver)); + bool hasFocus(enabled && (state & State_HasFocus)); + + // detect special buttons + bool inTabBar(widget && qobject_cast(widget->parentWidget())); + bool inToolButton(qstyleoption_cast(option)); + + // Style options + StyleOptions styleOptions(palette); + styleOptions.setMouseOver(mouseOver); + styleOptions.setHasFocus(hasFocus); + + // color + QColor color; + if (inTabBar) { + // for tabbar arrows one uses animations to get the arrow color + /* + * get animation state + * there is no need to update the engine since this was already done when rendering the frame + */ + AnimationMode mode(_animations->widgetStateEngine().buttonAnimationMode(widget)); + qreal opacity(_animations->widgetStateEngine().buttonOpacity(widget)); + + styleOptions.setAnimationMode(mode); + styleOptions.setOpacity(opacity); + + color = Colors::arrowOutlineColor(styleOptions); + } else if (mouseOver && !inToolButton) { + color = Colors::hoverColor(styleOptions); + } else if (inToolButton) { + bool flat(state & State_AutoRaise); + + // cast option + const QStyleOptionToolButton *toolButtonOption(static_cast(option)); + bool hasPopupMenu(toolButtonOption->subControls & SC_ToolButtonMenu); + if (flat && hasPopupMenu) { + // for menu arrows in flat toolbutton one uses animations to get the arrow color + // handle arrow over animation + bool arrowHover(mouseOver && (toolButtonOption->activeSubControls & SC_ToolButtonMenu)); + _animations->toolButtonEngine().updateState(widget, AnimationHover, arrowHover); + + bool animated(_animations->toolButtonEngine().isAnimated(widget, AnimationHover)); + qreal opacity(_animations->toolButtonEngine().opacity(widget, AnimationHover)); + + // Style ooptions + styleOptions.setAnimationMode(animated ? AnimationHover : AnimationNone); + + color = Colors::arrowOutlineColor(styleOptions); + } else { + bool sunken(state & (State_On | State_Sunken)); + if (flat) { + if (sunken && hasFocus && !mouseOver) { + color = palette.color(QPalette::HighlightedText); + } else { + // Style options + styleOptions.setColorRole(QPalette::WindowText); + + color = Colors::arrowOutlineColor(styleOptions); + } + } else if (hasFocus && !mouseOver) { + color = palette.color(QPalette::HighlightedText); + } else { + // Style options + styleOptions.setColorRole(QPalette::ButtonText); + + color = Colors::arrowOutlineColor(styleOptions); + } + } + } else { + // Style options + styleOptions.setColorRole(QPalette::WindowText); + + color = Colors::arrowOutlineColor(styleOptions); + } + + // render + _helper->renderArrow(painter, rect, color, orientation); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawIndicatorHeaderArrowPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *) const +{ + const QStyleOptionHeader *headerOption(qstyleoption_cast(option)); + const State &state(option->state); + + // arrow orientation + ArrowOrientation orientation(ArrowNone); + if (state & State_UpArrow || (headerOption && headerOption->sortIndicator == QStyleOptionHeader::SortUp)) { + orientation = ArrowUp; + } else if (state & State_DownArrow || (headerOption && headerOption->sortIndicator == QStyleOptionHeader::SortDown)) { + orientation = ArrowDown; + } + if (orientation == ArrowNone) { + return true; + } + + // invert arrows if requested by (hidden) options + if (Adwaita::Config::ViewInvertSortIndicator) { + orientation = (orientation == ArrowUp) ? ArrowDown : ArrowUp; + } + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setState(state); + + // define color and polygon for drawing arrow + QColor color = Colors::headerTextColor(styleOptions); + + // render + _helper->renderArrow(painter, option->rect, color, orientation); + + return true; +} + +//______________________________________________________________ +bool Style::drawPanelButtonCommandPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionButton *buttonOption(qstyleoption_cast< const QStyleOptionButton * >(option)); + if (!buttonOption) { + return true; + } + + // rect and palette + const QRect &rect(option->rect); + + // button state + const State &state(option->state); + bool enabled(state & State_Enabled); + bool windowActive(state & State_Active); + bool mouseOver((state & State_Active) && enabled && (state & State_MouseOver)); + bool hasFocus((enabled && (state & State_HasFocus)) && !(widget && widget->focusProxy())); + bool sunken(state & (State_On | State_Sunken)); + bool flat(buttonOption->features & QStyleOptionButton::Flat); + + // update animation state + // mouse over takes precedence over focus + _animations->widgetStateEngine().updateState(widget, AnimationPressed, sunken); + _animations->widgetStateEngine().updateState(widget, AnimationHover, mouseOver); + + AnimationMode mode(_animations->widgetStateEngine().buttonAnimationMode(widget)); + qreal opacity(_animations->widgetStateEngine().buttonOpacity(widget)); + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setMouseOver(mouseOver); + styleOptions.setHasFocus(hasFocus); + styleOptions.setSunken(sunken); + styleOptions.setOpacity(opacity); + styleOptions.setAnimationMode(mode); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + if (flat) { + // define colors and render + QColor color(Colors::toolButtonColor(styleOptions)); + _helper->renderToolButtonFrame(painter, rect, color, sunken); + } else { + // update button color from palette in case button is default + QPalette palette(option->palette); + if (enabled && buttonOption->features & QStyleOptionButton::DefaultButton) { + QColor button(palette.color(QPalette::Button)); + QColor base(palette.color(QPalette::Base)); + palette.setColor(QPalette::Button, Colors::mix(button, base, 0.7)); + } + + QColor shadow(palette.color(QPalette::Shadow)); + QColor outline(Colors::buttonOutlineColor(styleOptions)); + QColor background(Colors::buttonBackgroundColor(styleOptions)); + + // render + _helper->renderButtonFrame(painter, rect, background, outline, shadow, hasFocus, sunken, mouseOver, enabled && windowActive, _dark); + } + + return true; +} + +//______________________________________________________________ +bool Style::drawPanelButtonToolPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // copy palette and rect + const QPalette &palette(option->palette); + QRect rect(option->rect); + + // store relevant flags + const State &state(option->state); + bool autoRaise(state & State_AutoRaise); + bool enabled(state & State_Enabled); + bool windowActive(state & State_Active); + bool sunken(state & (State_On | State_Sunken)); + bool mouseOver((state & State_Active) && enabled && (option->state & State_MouseOver)); + bool hasFocus(enabled && (option->state & (State_HasFocus | State_Sunken))); + + /* + * get animation state + * no need to update, this was already done in drawToolButtonComplexControl + */ + AnimationMode mode(_animations->widgetStateEngine().buttonAnimationMode(widget)); + qreal opacity(_animations->widgetStateEngine().buttonOpacity(widget)); + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setMouseOver(mouseOver); + styleOptions.setHasFocus(hasFocus); + styleOptions.setSunken(sunken); + styleOptions.setOpacity(opacity); + styleOptions.setAnimationMode(mode); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + if (!autoRaise || mouseOver || sunken) { + // need to check widget for popup mode, because option is not set properly + const QToolButton *toolButton(qobject_cast(widget)); + bool hasPopupMenu(toolButton && toolButton->popupMode() == QToolButton::MenuButtonPopup); + + // render as push button + QColor shadow(Colors::shadowColor(styleOptions)); + QColor outline(Colors::buttonOutlineColor(styleOptions)); + QColor background(Colors::buttonBackgroundColor(styleOptions)); + + // adjust frame in case of menu + if (hasPopupMenu) { + painter->setClipRect(rect); + rect.adjust(0, 0, Metrics::Frame_FrameRadius + 2, 0); + rect = visualRect(option, rect); + } + + // render + _helper->renderButtonFrame(painter, rect, background, outline, shadow, hasFocus, sunken, mouseOver, windowActive); + } else { + QColor color(Colors::toolButtonColor(styleOptions)); + _helper->renderToolButtonFrame(painter, rect, color, sunken); + } + + return true; +} + +//______________________________________________________________ +bool Style::drawTabBarPanelButtonToolPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // copy palette and rect + QRect rect(option->rect); + + // static_cast is safe here since check was already performed in calling function + const QTabBar *tabBar(static_cast(widget->parentWidget())); + + // overlap. + // subtract 1, because of the empty pixel left the tabwidget frame + int overlap(Metrics::TabBar_BaseOverlap - 1); + + // adjust rect based on tabbar shape + switch (tabBar->shape()) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + rect.adjust(0, 0, 0, -overlap); + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + rect.adjust(0, overlap, 0, 0); + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + rect.adjust(0, 0, -overlap, 0); + break; + + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + rect.adjust(overlap, 0, 0, 0); + break; + + default: + break; + } + + // get the relevant palette + const QWidget *parent(tabBar->parentWidget()); + if (qobject_cast(parent)) { + parent = parent->parentWidget(); + } + QPalette palette(parent ? parent->palette() : QApplication::palette()); + + QColor color = hasAlteredBackground(parent) ? Colors::frameBackgroundColor(StyleOptions(palette)) : palette.color(QPalette::Window); + + // render flat background + painter->setPen(Qt::NoPen); + painter->setBrush(color); + painter->drawRect(rect); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawPanelScrollAreaCornerPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // make sure background role matches viewport + const QAbstractScrollArea *scrollArea; + if ((scrollArea = qobject_cast(widget)) && scrollArea->viewport()) { + // need to adjust clipRect in order not to render outside of frame + int frameWidth(pixelMetric(PM_DefaultFrameWidth, 0, scrollArea)); + painter->setClipRect(insideMargin(scrollArea->rect(), frameWidth)); + painter->setBrush(scrollArea->viewport()->palette().color(scrollArea->viewport()->backgroundRole())); + painter->setPen(Qt::NoPen); + painter->drawRect(option->rect); + return true; + } else { + return false; + } +} +//___________________________________________________________________________________ +bool Style::drawPanelMenuPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + /* + * do nothing if menu is embedded in another widget + * this corresponds to having a transparent background + */ + if (widget && !widget->isWindow()) { + return true; + } + + const QPalette &palette(option->palette); + QColor background(Colors::frameBackgroundColor(StyleOptions(palette))); + QColor outline(Colors::frameOutlineColor(StyleOptions(palette))); + + bool hasAlpha(_helper->hasAlphaChannel(widget)); + _helper->renderMenuFrame(painter, option->rect, background, outline, hasAlpha); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawPanelTipLabelPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + const QPalette &palette(option->palette); + QColor background(palette.color(QPalette::ToolTipBase)); + QColor outline(Colors::transparentize(QColor("black"), 0.3)); + bool hasAlpha(_helper->hasAlphaChannel(widget)); + + if (hasAlpha) { + int alpha = styleHint(SH_ToolTipLabel_Opacity, option, widget); + int h, s, l, a; + background.getHsl(&h, &s, &l, &a); + background = QColor::fromHsl(h, s, l, alpha); + } + + _helper->renderMenuFrame(painter, option->rect, background, outline, hasAlpha); + return true; +} + +//__________________________________________________________________________________ +bool Style::drawPanelItemViewRowPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + const QStyleOptionViewItem *vopt = qstyleoption_cast(option); + + if (!vopt) { + return false; + } + + QPalette::ColorGroup cg = (widget ? widget->isEnabled() : (vopt->state & QStyle::State_Enabled)) + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(vopt->state & QStyle::State_Active)) { + cg = QPalette::Inactive; + } + + if ((vopt->state & QStyle::State_Selected) && proxy()->styleHint(QStyle::SH_ItemView_ShowDecorationSelected, option, widget)) { + painter->fillRect(vopt->rect, vopt->palette.color(cg, QPalette::Highlight)); + } + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawPanelItemViewItemPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionViewItemV4 *viewItemOption = qstyleoption_cast(option); + if (!viewItemOption) { + return false; + } + + // try cast widget + const QAbstractItemView *abstractItemView = qobject_cast(widget); + + // store palette and rect + const QPalette &palette(option->palette); + QRect rect(option->rect); + + // store flags + const State &state(option->state); + bool mouseOver((state & State_Active) && (state & State_MouseOver) && (!abstractItemView || abstractItemView->selectionMode() != QAbstractItemView::NoSelection)); + bool selected(state & State_Selected); + bool enabled(state & State_Enabled); + bool windowActive(state & State_Active); + + bool hasCustomBackground = viewItemOption->backgroundBrush.style() != Qt::NoBrush && !(state & State_Selected); + bool hasSolidBackground = !hasCustomBackground || viewItemOption->backgroundBrush.style() == Qt::SolidPattern; + + // do nothing if no background is to be rendered + if (!(selected || hasCustomBackground)) { + return true; + } + + // define color group + QPalette::ColorGroup colorGroup; + if (enabled) { + colorGroup = windowActive ? QPalette::Active : QPalette::Inactive; + } else { + colorGroup = QPalette::Disabled; + } + + // render custom background + if (hasCustomBackground && !hasSolidBackground) { + painter->setBrushOrigin(viewItemOption->rect.topLeft()); + painter->setBrush(viewItemOption->backgroundBrush); + painter->setPen(Qt::NoPen); + painter->drawRect(viewItemOption->rect); + return true; + } + + // render selection + // define color + QColor color; + if (hasCustomBackground && hasSolidBackground) { + color = viewItemOption->backgroundBrush.color(); + } else { + color = palette.color(colorGroup, QPalette::Highlight); + } + + // render + _helper->renderSelection(painter, rect, color); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawIndicatorCheckBoxPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // copy rect and palette + const QRect &rect(option->rect.adjusted(1, 1, -1, -1)); + const QPalette &palette(option->palette); + + // store flags + const State &state(option->state); + bool enabled(state & State_Enabled); + bool mouseOver((state & State_Active) && enabled && (state & State_MouseOver)); + bool sunken(enabled && (state & State_Sunken)); + bool active((state & (State_On | State_NoChange))); + bool windowActive(state & State_Active); + + // checkbox state + CheckBoxState checkBoxState(CheckOff); + if (state & State_NoChange) { + checkBoxState = CheckPartial; + } else if (state & State_On) { + checkBoxState = CheckOn; + } + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setMouseOver(mouseOver); + styleOptions.setHasFocus(false); + styleOptions.setSunken(sunken); + styleOptions.setOpacity(AnimationData::OpacityInvalid); + styleOptions.setAnimationMode(AnimationNone); + styleOptions.setCheckboxState(checkBoxState); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + const QColor &outline(Colors::indicatorOutlineColor(styleOptions)); + const QColor &background(Colors::indicatorBackgroundColor(styleOptions)); + + // detect checkboxes in lists + bool isSelectedItem(this->isSelectedItem(widget, rect.center())); + + // animation state + _animations->widgetStateEngine().updateState(widget, AnimationHover, mouseOver); + if (checkBoxState != CheckPartial) { + _animations->widgetStateEngine().updateState(widget, AnimationPressed, checkBoxState != CheckOff); + if (_animations->widgetStateEngine().isAnimated(widget, AnimationPressed) && checkBoxState == CheckOn) { + checkBoxState = CheckAnimated; + } + } + qreal animation(_animations->widgetStateEngine().opacity(widget, AnimationPressed)); + + QColor tickColor; + if (isSelectedItem) { + // Style options + styleOptions.setActive(enabled && active); + + tickColor = Colors::checkBoxIndicatorColor(styleOptions); + _helper->renderCheckBoxBackground(painter, rect, palette.color(QPalette::Base), outline, sunken); + } else { + AnimationMode mode(_animations->widgetStateEngine().isAnimated(widget, AnimationHover) ? AnimationHover : AnimationNone); + qreal opacity(_animations->widgetStateEngine().opacity(widget, AnimationHover)); + + // Style options + styleOptions.setAnimationMode(mode); + styleOptions.setOpacity(opacity); + styleOptions.setActive(enabled && active); + + tickColor = Colors::checkBoxIndicatorColor(styleOptions); + } + + // render + QColor shadow(Colors::shadowColor(StyleOptions(palette))); + _helper->renderCheckBox(painter, rect, background, outline, tickColor, sunken, checkBoxState, mouseOver, animation, enabled && windowActive, _dark); + return true; +} + +//___________________________________________________________________________________ +bool Style::drawIndicatorRadioButtonPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // copy rect and palette + const QRect &rect(option->rect); + const QPalette &palette(option->palette); + + // store flags + const State &state(option->state); + bool enabled(state & State_Enabled); + bool mouseOver((state & State_Active) && enabled && (state & State_MouseOver)); + bool sunken(state & State_Sunken); + bool checked(state & State_On); + bool windowActive(state & State_Active); + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setMouseOver(mouseOver); + styleOptions.setHasFocus(false); + styleOptions.setSunken(sunken); + styleOptions.setOpacity(AnimationData::OpacityInvalid); + styleOptions.setAnimationMode(AnimationNone); + styleOptions.setCheckboxState(checked ? CheckOn : CheckOff); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + const QColor &outline(Colors::indicatorOutlineColor(styleOptions)); + const QColor &background(Colors::indicatorBackgroundColor(styleOptions)); + + // radio button state + RadioButtonState radioButtonState(state & State_On ? RadioOn : RadioOff); + + // detect radiobuttons in lists + bool isSelectedItem(this->isSelectedItem(widget, rect.center())); + + // animation state + _animations->widgetStateEngine().updateState(widget, AnimationHover, mouseOver); + _animations->widgetStateEngine().updateState(widget, AnimationPressed, radioButtonState != RadioOff); + if (_animations->widgetStateEngine().isAnimated(widget, AnimationPressed)) { + radioButtonState = RadioAnimated; + } + qreal animation(_animations->widgetStateEngine().opacity(widget, AnimationPressed)); + + // colors + QColor shadow(Colors::shadowColor(StyleOptions(palette))); + QColor tickColor; + if (isSelectedItem) { + // Style options + styleOptions.setActive(enabled && checked); + + tickColor = Colors::checkBoxIndicatorColor(styleOptions); + _helper->renderRadioButtonBackground(painter, rect, palette.color(QPalette::Base), outline, sunken); + } else { + AnimationMode mode(_animations->widgetStateEngine().isAnimated(widget, AnimationHover) ? AnimationHover : AnimationNone); + qreal opacity(_animations->widgetStateEngine().opacity(widget, AnimationHover)); + + // Style options + styleOptions.setAnimationMode(mode); + styleOptions.setOpacity(opacity); + styleOptions.setActive(enabled && checked); + + tickColor = Colors::checkBoxIndicatorColor(styleOptions); + } + + // render + _helper->renderRadioButton(painter, rect, background, outline, tickColor, sunken, enabled && windowActive, radioButtonState, animation, mouseOver, _dark, false); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawIndicatorButtonDropDownPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionToolButton *toolButtonOption(qstyleoption_cast(option)); + if (!toolButtonOption) { + return true; + } + + // store state + const State &state(option->state); + bool autoRaise(state & State_AutoRaise); + bool enabled(state & State_Enabled); + bool windowActive(state & State_Active); + bool hasFocus(enabled && (state & (State_HasFocus | State_Sunken))); + bool mouseOver((state & State_Active) && enabled && (state & State_MouseOver)); + bool sunken(enabled && (state & State_Sunken)); + + // do nothing for autoraise buttons + if ((autoRaise && !sunken && !mouseOver) || !(toolButtonOption->subControls & SC_ToolButtonMenu)) { + return true; + } + + // store palette and rect + const QPalette &palette(option->palette); + const QRect &rect(option->rect); + + // update animation state + // mouse over takes precedence over focus + _animations->widgetStateEngine().updateState(widget, AnimationPressed, sunken); + _animations->widgetStateEngine().updateState(widget, AnimationHover, mouseOver); + + AnimationMode mode(_animations->widgetStateEngine().buttonAnimationMode(widget)); + qreal opacity(_animations->widgetStateEngine().buttonOpacity(widget)); + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setMouseOver(mouseOver); + styleOptions.setHasFocus(false); + styleOptions.setSunken(sunken); + styleOptions.setOpacity(AnimationData::OpacityInvalid); + styleOptions.setAnimationMode(AnimationNone); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + // render as push button + QColor shadow(Colors::shadowColor(StyleOptions(palette))); + QColor outline(Colors::buttonOutlineColor(styleOptions)); + QColor background(Colors::buttonBackgroundColor(styleOptions)); + + QRect frameRect(rect); + painter->setClipRect(rect); + frameRect.adjust(-Metrics::Frame_FrameRadius - 1, 0, 0, 0); + frameRect = visualRect(option, frameRect); + + // render + _helper->renderButtonFrame(painter, frameRect, background, outline, shadow, hasFocus, sunken, mouseOver, windowActive); + + // also render separator + QRect separatorRect(rect.adjusted(0, 2, -2, -2)); + separatorRect.setWidth(1); + separatorRect = visualRect(option, separatorRect); + _helper->renderSeparator(painter, separatorRect, outline, true); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawIndicatorTabClosePrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // get icon and check + QIcon icon(standardIcon(SP_TitleBarCloseButton, option, widget)); + if (icon.isNull()) { + return false; + } + + // store state + const State &state(option->state); + bool enabled(state & State_Enabled); + bool active(state & State_Raised); + bool sunken(state & State_Sunken); + + // decide icon mode and state + QIcon::Mode iconMode; + QIcon::State iconState; + if (!enabled) { + iconMode = QIcon::Disabled; + iconState = QIcon::Off; + } else { + if (active) { + iconMode = QIcon::Active; + } else { + iconMode = QIcon::Normal; + } + + iconState = sunken ? QIcon::On : QIcon::Off; + } + + // icon size + int iconWidth(pixelMetric(QStyle::PM_SmallIconSize, option, widget)); + QSize iconSize(iconWidth, iconWidth); + + // get pixmap + QPixmap pixmap(icon.pixmap(iconSize, iconMode, iconState)); + + // render + drawItemPixmap(painter, option->rect, Qt::AlignCenter, pixmap); + return true; +} + +//___________________________________________________________________________________ +bool Style::drawIndicatorTabTearPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *) const +{ + // cast option and check + const QStyleOptionTab *tabOption(qstyleoption_cast(option)); + if (!tabOption) { + return true; + } + + // store palette and rect + const QPalette &palette(option->palette); + QRect rect(option->rect); + + bool reverseLayout(option->direction == Qt::RightToLeft); + + QColor color(Colors::alphaColor(palette.color(QPalette::WindowText), 0.2)); + painter->setRenderHint(QPainter::Antialiasing, false); + painter->setPen(color); + painter->setBrush(Qt::NoBrush); + switch (tabOption->shape) { + case QTabBar::TriangularNorth: + case QTabBar::RoundedNorth: + rect.adjust(0, 1, 0, 0); + if (reverseLayout) { + painter->drawLine(rect.topRight(), rect.bottomRight()); + } else { + painter->drawLine(rect.topLeft(), rect.bottomLeft()); + } + break; + case QTabBar::TriangularSouth: + case QTabBar::RoundedSouth: + rect.adjust(0, 0, 0, -1); + if (reverseLayout) { + painter->drawLine(rect.topRight(), rect.bottomRight()); + } else { + painter->drawLine(rect.topLeft(), rect.bottomLeft()); + } + break; + case QTabBar::TriangularWest: + case QTabBar::RoundedWest: + rect.adjust(1, 0, 0, 0); + painter->drawLine(rect.topLeft(), rect.topRight()); + break; + case QTabBar::TriangularEast: + case QTabBar::RoundedEast: + rect.adjust(0, 0, -1, 0); + painter->drawLine(rect.topLeft(), rect.topRight()); + break; + + default: + break; + } + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawIndicatorToolBarHandlePrimitive(const QStyleOption *option, QPainter *painter, const QWidget *) const +{ + // do nothing if disabled from options + if (!Adwaita::Config::ToolBarDrawItemSeparator) { + return true; + } + + // store rect and palette + QRect rect(option->rect); + const QPalette &palette(option->palette); + + // store state + const State &state(option->state); + bool separatorIsVertical(state & State_Horizontal); + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + // define color and render + QColor color(Colors::separatorColor(styleOptions)); + if (separatorIsVertical) { + rect.setWidth(Metrics::ToolBar_HandleWidth); + rect = centerRect(option->rect, rect.size()); + rect.setWidth(3); + _helper->renderSeparator(painter, rect, color, separatorIsVertical); + + rect.translate(2, 0); + _helper->renderSeparator(painter, rect, color, separatorIsVertical); + } else { + rect.setHeight(Metrics::ToolBar_HandleWidth); + rect = centerRect(option->rect, rect.size()); + rect.setHeight(3); + _helper->renderSeparator(painter, rect, color, separatorIsVertical); + + rect.translate(0, 2); + _helper->renderSeparator(painter, rect, color, separatorIsVertical); + } + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawIndicatorToolBarSeparatorPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + /* + * do nothing if disabled from options + * also need to check if widget is a combobox, because of Qt hack using 'toolbar' separator primitive + * for rendering separators in comboboxes + */ + if (!(Adwaita::Config::ToolBarDrawItemSeparator || qobject_cast(widget))) { + return true; + } + + // store rect and palette + const QRect &rect(option->rect); + const QPalette &palette(option->palette); + + // store state + const State &state(option->state); + bool separatorIsVertical(state & State_Horizontal); + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + // define color and render + QColor color(Colors::separatorColor(styleOptions)); + _helper->renderSeparator(painter, rect, color, separatorIsVertical); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawIndicatorBranchPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *) const +{ + // copy rect and palette + const QRect &rect(option->rect); + const QPalette &palette(option->palette); + + // state + const State &state(option->state); + bool reverseLayout(option->direction == Qt::RightToLeft); + + //draw expander + int expanderAdjust = 0; + if (state & State_Children) { + // state + bool expanderOpen(state & State_Open); + bool enabled(state & State_Enabled); + bool mouseOver((state & State_Active) && enabled && (state & State_MouseOver)); + + // expander rect + int expanderSize = qMin(rect.width(), rect.height()); + expanderSize = qMin(expanderSize, int(Metrics::ItemView_ArrowSize)); + expanderAdjust = expanderSize / 2 + 1; + QRect arrowRect = centerRect(rect, expanderSize, expanderSize); + + // get orientation from option + ArrowOrientation orientation; + if (expanderOpen) { + orientation = ArrowDown; + } else if (reverseLayout) { + orientation = ArrowLeft; + } else { + orientation = ArrowRight; + } + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setColorRole(QPalette::Text); + + // color + QColor arrowColor(mouseOver ? Colors::hoverColor(StyleOptions(palette)) : Colors::arrowOutlineColor(styleOptions)); + + // render + _helper->renderArrow(painter, arrowRect, arrowColor, orientation); + } + + // tree branches + if (!Adwaita::Config::ViewDrawTreeBranchLines) { + return true; + } + + QPoint center(rect.center()); + QColor lineColor(Colors::mix(palette.color(QPalette::Base), palette.color(QPalette::Text), 0.25)); + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->translate(0.5, 0.5); + painter->setPen(QPen(lineColor, 1)); + if (state & (State_Item | State_Children | State_Sibling)) { + QLineF line(QPointF(center.x(), rect.top()), QPointF(center.x(), center.y() - expanderAdjust - 1)); + painter->drawLine(line); + } + + // The right/left (depending on direction) line gets drawn if we have an item + if (state & State_Item) { + const QLineF line = reverseLayout ? + QLineF(QPointF(rect.left(), center.y()), QPointF(center.x() - expanderAdjust, center.y())) : + QLineF(QPointF(center.x() + expanderAdjust, center.y()), QPointF(rect.right(), center.y())); + painter->drawLine(line); + } + + // The bottom if we have a sibling + if (state & State_Sibling) { + QLineF line(QPointF(center.x(), center.y() + expanderAdjust), QPointF(center.x(), rect.bottom())); + painter->drawLine(line); + } + painter->restore(); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawPushButtonLabelControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionButton *buttonOption(qstyleoption_cast(option)); + if (!buttonOption) { + return true; + } + + // copy rect and palette + const QRect &rect(option->rect); + const QPalette &palette(option->palette); + + // state + const State &state(option->state); + bool enabled(state & State_Enabled); + bool sunken(state & (State_On | State_Sunken)); + bool mouseOver((state & State_Active) && enabled && (option->state & State_MouseOver)); + bool hasFocus(enabled && !mouseOver && (option->state & State_HasFocus)); + bool flat(buttonOption->features & QStyleOptionButton::Flat); + + // content + bool hasText(!buttonOption->text.isEmpty()); + bool hasIcon((showIconsOnPushButtons() || flat || !hasText) && !buttonOption->icon.isNull()); + + // contents + QRect contentsRect(rect); + + // color role + QPalette::ColorRole textRole; + if (flat) { + if (hasFocus && sunken) { + textRole = QPalette::ButtonText; + } else { + textRole = QPalette::WindowText; + } + } else if (hasFocus) { + textRole = QPalette::ButtonText; + } else { + textRole = QPalette::ButtonText; + } + + // menu arrow + if (buttonOption->features & QStyleOptionButton::HasMenu) { + // define rect + QRect arrowRect(contentsRect); + arrowRect.setLeft(contentsRect.right() - Metrics::MenuButton_IndicatorWidth + 1); + arrowRect = centerRect(arrowRect, Metrics::MenuButton_IndicatorWidth, Metrics::MenuButton_IndicatorWidth); + + contentsRect.setRight(arrowRect.left() - Metrics::Button_ItemSpacing - 1); + contentsRect.adjust(Metrics::Button_MarginWidth, 0, 0, 0); + + arrowRect = visualRect(option, arrowRect); + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setColorRole(textRole); + + // define color + QColor arrowColor(Colors::arrowOutlineColor(styleOptions)); + _helper->renderArrow(painter, arrowRect, arrowColor, ArrowDown); + } + + // icon size + QSize iconSize; + if (hasIcon) { + iconSize = buttonOption->iconSize; + if (!iconSize.isValid()) { + int metric(pixelMetric(PM_SmallIconSize, option, widget)); + iconSize = QSize(metric, metric); + } + } + + // text size + int textFlags(_mnemonics->textFlags() | Qt::AlignCenter); + QSize textSize(option->fontMetrics.size(textFlags, buttonOption->text)); + + // adjust text and icon rect based on options + QRect iconRect; + QRect textRect; + + if (hasText && !hasIcon) { + textRect = contentsRect; + } else if (hasIcon && !hasText) { + iconRect = contentsRect; + } else { + int contentsWidth(iconSize.width() + textSize.width() + Metrics::Button_ItemSpacing); + iconRect = QRect(QPoint(contentsRect.left() + (contentsRect.width() - contentsWidth) / 2, contentsRect.top() + (contentsRect.height() - iconSize.height()) / 2), iconSize); + textRect = QRect(QPoint(iconRect.right() + Metrics::ToolButton_ItemSpacing + 1, contentsRect.top() + (contentsRect.height() - textSize.height()) / 2), textSize); + } + + // handle right to left + if (iconRect.isValid()) { + iconRect = visualRect(option, iconRect); + } + if (textRect.isValid()) { + textRect = visualRect(option, textRect); + } + + // make sure there is enough room for icon + if (iconRect.isValid()) { + iconRect = centerRect(iconRect, iconSize); + } + + // render icon + if (hasIcon && iconRect.isValid()) { + // icon state and mode + const QIcon::State iconState(sunken ? QIcon::On : QIcon::Off); + QIcon::Mode iconMode; + if (!enabled) { + iconMode = QIcon::Disabled; + } else if (!flat && hasFocus) { + iconMode = QIcon::Selected; + } else if (mouseOver && flat) { + iconMode = QIcon::Active; + } else { + iconMode = QIcon::Normal; + } + + QPixmap pixmap = buttonOption->icon.pixmap(iconSize, iconMode, iconState); + drawItemPixmap(painter, iconRect, Qt::AlignCenter, pixmap); + } + + // render text + if (hasText && textRect.isValid()) { + if (enabled && !sunken && !mouseOver && !flat) { + if (_dark) { + drawItemText(painter, textRect.adjusted(0, -1, 0, -1), textFlags, palette, false, buttonOption->text, QPalette::Dark); + } else { + drawItemText(painter, textRect.adjusted(0, 1, 0, 1), textFlags, palette, false, buttonOption->text, QPalette::Light); + } + } + drawItemText(painter, textRect, textFlags, palette, enabled, buttonOption->text, textRole); + } + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawToolButtonLabelControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionToolButton *toolButtonOption(qstyleoption_cast(option)); + + // copy rect and palette + const QRect &rect = option->rect; + const QPalette &palette = option->palette; + + // state + const State &state(option->state); + bool enabled(state & State_Enabled); + bool sunken(state & (State_On | State_Sunken)); + bool mouseOver((state & State_Active) && enabled && (option->state & State_MouseOver)); + bool flat(state & State_AutoRaise); + + // focus flag is set to match the background color in either renderButtonFrame or renderToolButtonFrame + bool hasFocus(false); + if (flat) { + hasFocus = enabled && !mouseOver && (option->state & State_HasFocus); + } else { + hasFocus = enabled && !mouseOver && (option->state & (State_HasFocus | State_Sunken)); + } + + bool hasArrow(toolButtonOption->features & QStyleOptionToolButton::Arrow); + bool hasIcon(!(hasArrow || toolButtonOption->icon.isNull())); + bool hasText(!toolButtonOption->text.isEmpty()); + + // contents + QRect contentsRect(rect); + + // icon size + QSize iconSize(toolButtonOption->iconSize); + + // text size + int textFlags(_mnemonics->textFlags()); + QSize textSize(option->fontMetrics.size(textFlags, toolButtonOption->text)); + + // adjust text and icon rect based on options + QRect iconRect; + QRect textRect; + + if (hasText && (!(hasArrow || hasIcon) || toolButtonOption->toolButtonStyle == Qt::ToolButtonTextOnly)) { + // text only + textRect = contentsRect; + textFlags |= Qt::AlignCenter; + } else if ((hasArrow || hasIcon) && (!hasText || toolButtonOption->toolButtonStyle == Qt::ToolButtonIconOnly)) { + // icon only + iconRect = contentsRect; + } else if (toolButtonOption->toolButtonStyle == Qt::ToolButtonTextUnderIcon) { + int contentsHeight(iconSize.height() + textSize.height() + Metrics::ToolButton_ItemSpacing); + iconRect = QRect(QPoint(contentsRect.left() + (contentsRect.width() - iconSize.width()) / 2, contentsRect.top() + (contentsRect.height() - contentsHeight) / 2), iconSize); + textRect = QRect(QPoint(contentsRect.left() + (contentsRect.width() - textSize.width()) / 2, iconRect.bottom() + Metrics::ToolButton_ItemSpacing + 1), textSize); + textFlags |= Qt::AlignCenter; + } else { + bool leftAlign(widget && widget->property(PropertyNames::toolButtonAlignment).toInt() == Qt::AlignLeft); + if (leftAlign) { + iconRect = QRect(QPoint(contentsRect.left(), contentsRect.top() + (contentsRect.height() - iconSize.height()) / 2), iconSize); + } else { + int contentsWidth(iconSize.width() + textSize.width() + Metrics::ToolButton_ItemSpacing); + iconRect = QRect(QPoint(contentsRect.left() + (contentsRect.width() - contentsWidth) / 2, contentsRect.top() + (contentsRect.height() - iconSize.height()) / 2), iconSize); + } + + textRect = QRect(QPoint(iconRect.right() + Metrics::ToolButton_ItemSpacing + 1, contentsRect.top() + (contentsRect.height() - textSize.height()) / 2), textSize); + + // handle right to left layouts + iconRect = visualRect(option, iconRect); + textRect = visualRect(option, textRect); + + textFlags |= Qt::AlignLeft | Qt::AlignVCenter; + } + + // make sure there is enough room for icon + if (iconRect.isValid()) { + iconRect = centerRect(iconRect, iconSize); + } + + // render arrow or icon + if (hasArrow && iconRect.isValid()) { + QStyleOptionToolButton copy(*toolButtonOption); + copy.rect = iconRect; + switch (toolButtonOption->arrowType) { + case Qt::LeftArrow: + drawPrimitive(PE_IndicatorArrowLeft, ©, painter, widget); + break; + case Qt::RightArrow: + drawPrimitive(PE_IndicatorArrowRight, ©, painter, widget); + break; + case Qt::UpArrow: + drawPrimitive(PE_IndicatorArrowUp, ©, painter, widget); + break; + case Qt::DownArrow: + drawPrimitive(PE_IndicatorArrowDown, ©, painter, widget); + break; + default: + break; + } + + } else if (hasIcon && iconRect.isValid()) { + // icon state and mode + const QIcon::State iconState(sunken ? QIcon::On : QIcon::Off); + QIcon::Mode iconMode; + if (!enabled) { + iconMode = QIcon::Disabled; + } else if (!flat && hasFocus) { + iconMode = QIcon::Selected; + } else if (mouseOver && flat) { + iconMode = QIcon::Active; + } else { + iconMode = QIcon::Normal; + } + + QPixmap pixmap = toolButtonOption->icon.pixmap(iconSize, iconMode, iconState); + drawItemPixmap(painter, iconRect, Qt::AlignCenter, pixmap); + } + + // render text + if (hasText && textRect.isValid()) { + QPalette::ColorRole textRole(QPalette::ButtonText); + if (flat) { + textRole = (hasFocus && sunken && !mouseOver) ? QPalette::HighlightedText : QPalette::WindowText; + } else if (hasFocus && !mouseOver) { + textRole = QPalette::HighlightedText; + } + + painter->setFont(toolButtonOption->font); + drawItemText(painter, textRect, textFlags, palette, enabled, toolButtonOption->text, textRole); + } + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawCheckBoxLabelControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionButton *buttonOption(qstyleoption_cast(option)); + if (!buttonOption) { + return true; + } + + // copy palette and rect + const QPalette &palette(option->palette); + const QRect &rect(option->rect); + + // store state + const State &state(option->state); + bool enabled(state & State_Enabled); + + // text alignment + bool reverseLayout(option->direction == Qt::RightToLeft); + int textFlags(_mnemonics->textFlags() | Qt::AlignVCenter | (reverseLayout ? Qt::AlignRight : Qt::AlignLeft)); + + // text rect + QRect textRect(rect); + + // render icon + if (!buttonOption->icon.isNull()) { + const QIcon::Mode mode(enabled ? QIcon::Normal : QIcon::Disabled); + QPixmap pixmap(buttonOption->icon.pixmap(buttonOption->iconSize, mode)); + drawItemPixmap(painter, rect, textFlags, pixmap); + + // adjust rect (copied from QCommonStyle) + textRect.setLeft(textRect.left() + buttonOption->iconSize.width() + 4); + textRect = visualRect(option, textRect); + } + + // render text + if (!buttonOption->text.isEmpty()) { + textRect = option->fontMetrics.boundingRect(textRect, textFlags, buttonOption->text); + drawItemText(painter, textRect, textFlags, palette, enabled, buttonOption->text, QPalette::Text); + + // check focus state + bool hasFocus(enabled && (state & State_HasFocus)); + + // update animation state + _animations->widgetStateEngine().updateState(widget, AnimationFocus, hasFocus); + bool isFocusAnimated(_animations->widgetStateEngine().isAnimated(widget, AnimationFocus)); + qreal opacity(_animations->widgetStateEngine().opacity(widget, AnimationFocus)); + } + + return true; +} + + +//___________________________________________________________________________________ +bool Style::drawComboBoxLabelControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + const QStyleOptionComboBox *comboBoxOption(qstyleoption_cast(option)); + if (!comboBoxOption) { + return false; + } + if (comboBoxOption->editable) { + return false; + } + + // need to alter palette for focused buttons + const State &state(option->state); + bool enabled(state & State_Enabled); + bool sunken(state & (State_On | State_Sunken)); + bool mouseOver((state & State_Active) && enabled && (option->state & State_MouseOver)); + bool hasFocus(enabled && !mouseOver && (option->state & State_HasFocus)); + bool flat(!comboBoxOption->frame); + + QPalette::ColorRole textRole = QPalette::ButtonText; + + // change pen color directly + painter->save(); + painter->setPen(QPen(option->palette.color(textRole), 1)); + + if (const QStyleOptionComboBox *cb = qstyleoption_cast(option)) { + QRect editRect = proxy()->subControlRect(CC_ComboBox, cb, SC_ComboBoxEditField, widget); + painter->save(); + painter->setClipRect(editRect); + if (!cb->currentIcon.isNull() && qobject_cast(widget)) { + QIcon::Mode mode; + + if ((cb->state & QStyle::State_Selected) && (cb->state & QStyle::State_Active)) { + mode = QIcon::Selected; + } else if (cb->state & QStyle::State_Enabled) { + mode = QIcon::Normal; + } else { + mode = QIcon::Disabled; + } + + QPixmap pixmap = cb->currentIcon.pixmap(widget->windowHandle(), cb->iconSize, mode); + QRect iconRect(editRect); + iconRect.setWidth(cb->iconSize.width() + 4); + iconRect = alignedRect(cb->direction, + Qt::AlignLeft | Qt::AlignVCenter, + iconRect.size(), editRect); + if (cb->editable) { + painter->fillRect(iconRect, option->palette.brush(QPalette::Base)); + } + proxy()->drawItemPixmap(painter, iconRect, Qt::AlignCenter, pixmap); + + if (cb->direction == Qt::RightToLeft) { + editRect.translate(-4 - cb->iconSize.width(), 0); + } else { + editRect.translate(cb->iconSize.width() + 4, 0); + } + } + if (!cb->currentText.isEmpty() && !cb->editable) { + proxy()->drawItemText(painter, editRect.adjusted(Metrics::ComboBox_MarginWidth, 0, -1, 0), + visualAlignment(cb->direction, Qt::AlignLeft | Qt::AlignVCenter), + cb->palette, cb->state & State_Enabled, cb->currentText); + } + painter->restore(); + } + + painter->restore(); + + return true; +} + +// +bool Style::drawItemViewItemControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + const QStyleOptionViewItem *vopt = qstyleoption_cast(option); + if (!vopt) { + return true; + } + + QStyleOptionViewItem op(*vopt); + + if (_helper->isWindowActive(widget)) { + const QColor activeTextColor = _dark ? QColor("#eeeeec") : QColor("#2e3436"); + const QColor inactiveTextColor = _dark ? Colors::mix(QColor("#eeeeec"), Colors::darken(Colors::desaturate(QColor("#3d3846"), 1.0), 0.04)) : + Colors::mix(QColor("#2e3436"), QColor("#f6f5f4")); + // No custom text color used, we can do our HACK + QPalette palette = op.palette; + if (inactiveTextColor == palette.color(QPalette::Inactive, QPalette::Text) && activeTextColor == palette.color(QPalette::Active, QPalette::Text)) { + palette.setColor(QPalette::Inactive, QPalette::Text, palette.color(QPalette::Active, QPalette::Text)); + op.palette = palette; + } + } + + ParentStyleClass::drawControl(CE_ItemViewItem, &op, painter, widget); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawMenuBarEmptyArea(const QStyleOption *option, QPainter *painter, const QWidget *) const +{ + const QRect &rect(option->rect); + const QPalette &palette(option->palette); + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, false); + painter->setBrush(Qt::NoBrush); + painter->setPen(Colors::mix(palette.color(QPalette::Window), palette.color(QPalette::Shadow), 0.2)); + + painter->drawLine(rect.bottomLeft(), rect.bottomRight()); + painter->restore(); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawMenuBarItemControl(const QStyleOption *option, QPainter *painter, const QWidget *) const +{ + + // cast option and check + const QStyleOptionMenuItem *menuItemOption = qstyleoption_cast(option); + if (!menuItemOption) + return true; + + // copy rect and palette + const QRect &rect(option->rect); + const QPalette &palette(option->palette); + + // store state + const State &state(option->state); + bool enabled(state & State_Enabled); + bool selected(enabled && (state & State_Selected)); + bool sunken(enabled && (state & State_Sunken)); + bool useStrongFocus(Adwaita::Config::MenuItemDrawStrongFocus); + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, false); + + painter->setBrush(palette.window().color()); + painter->setPen(Qt::NoPen); + painter->drawRect(rect); + + painter->setBrush(Qt::NoBrush); + painter->setPen(Colors::mix(palette.color(QPalette::Window), palette.color(QPalette::Shadow), 0.2)); + + painter->drawLine(rect.bottomLeft(), rect.bottomRight()); + painter->restore(); + + // render hover and focus + if (useStrongFocus && sunken) { + QColor outlineColor = Colors::focusColor(StyleOptions(palette)); + _helper->renderFocusRect(painter, QRect(rect.left(), rect.bottom() - 2, rect.width(), 3), outlineColor); + } + + // get text rect + int textFlags(Qt::AlignCenter | _mnemonics->textFlags()); + QRect textRect = option->fontMetrics.boundingRect(rect, textFlags, menuItemOption->text); + + // render text + const QPalette::ColorRole role = (useStrongFocus && sunken) ? QPalette::Link : QPalette::WindowText; + drawItemText(painter, textRect, textFlags, palette, enabled, menuItemOption->text, role); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawMenuItemControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionMenuItem *menuItemOption = qstyleoption_cast(option); + if (!menuItemOption) { + return true; + } + if (menuItemOption->menuItemType == QStyleOptionMenuItem::EmptyArea) { + return true; + } + + // copy rect and palette + const QRect &rect(option->rect); + const QPalette &palette(option->palette); + + // deal with separators + if (menuItemOption->menuItemType == QStyleOptionMenuItem::Separator) { + // normal separator + if (menuItemOption->text.isEmpty() && menuItemOption->icon.isNull()) { + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + QColor color(Colors::separatorColor(styleOptions)); + _helper->renderSeparator(painter, rect, color); + return true; + } else { + /* + * separator can have a title and an icon + * in that case they are rendered as menu title buttons + */ + QStyleOptionToolButton copy(separatorMenuItemOption(menuItemOption, widget)); + renderMenuTitle(©, painter, widget); + + return true; + } + } + + // store state + const State &state(option->state); + bool enabled(state & State_Enabled); + bool windowActive(state & State_Active); + bool selected(enabled && (state & State_Selected)); + bool mouseOver((state & State_Active) && enabled && (state & State_MouseOver)); + bool sunken(enabled && (state & (State_On | State_Sunken))); + bool reverseLayout(option->direction == Qt::RightToLeft); + bool useStrongFocus(Adwaita::Config::MenuItemDrawStrongFocus); + + // render hover and focus + if (useStrongFocus && (selected || sunken)) { + QColor color = Colors::focusColor(StyleOptions(palette)); + QColor outlineColor = Qt::transparent; + + Sides sides = 0; + _helper->renderFocusRect(painter, rect, color, outlineColor, sides); + } + + // get rect available for contents + QRect contentsRect(insideMargin(rect, Metrics::MenuItem_MarginWidth)); + + // define relevant rectangles + // checkbox + QRect checkBoxRect; + if (menuItemOption->menuHasCheckableItems) { + checkBoxRect = QRect(contentsRect.left(), contentsRect.top() + (contentsRect.height() - Metrics::CheckBox_Size) / 2, Metrics::CheckBox_Size, Metrics::CheckBox_Size).adjusted(1, 1, -1, -1); + } + + // We want to always to keep the space for checkbox + contentsRect.setLeft(rect.left() + Metrics::CheckBox_Size + Metrics::MenuItem_ItemSpacing); + + CheckBoxState checkState(menuItemOption->checked ? CheckOn : CheckOff); + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setMouseOver(mouseOver); + styleOptions.setHasFocus(false); + styleOptions.setSunken(false); + styleOptions.setOpacity(AnimationData::OpacityInvalid); + styleOptions.setAnimationMode(AnimationNone); + styleOptions.setCheckboxState(checkState); + styleOptions.setInMenu(true); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + const QColor &outline(palette.foreground().color()); + const QColor &indicatorBackground(Colors::indicatorBackgroundColor(styleOptions)); + // render checkbox indicator + if (menuItemOption->checkType == QStyleOptionMenuItem::NonExclusive) { + checkBoxRect = visualRect(option, checkBoxRect); + + // checkbox state + + /* + if( useStrongFocus && ( selected || sunken ) ) + { _helper->renderCheckBoxBackground( painter, checkBoxRect, palette.color( QPalette::Window ), outline, sunken ); } + */ + + bool active(menuItemOption->checked); + AnimationMode mode(_animations->widgetStateEngine().isAnimated(widget, AnimationHover) ? AnimationHover : AnimationNone); + qreal opacity(_animations->widgetStateEngine().opacity(widget, AnimationHover)); + + // Style options + styleOptions.setActive(enabled && active); + styleOptions.setAnimationMode(mode); + styleOptions.setOpacity(opacity); + + QColor tickColor = Colors::checkBoxIndicatorColor(styleOptions); + _helper->renderCheckBox(painter, checkBoxRect, indicatorBackground, outline, tickColor, false, checkState, mouseOver, enabled && windowActive, _dark, true); + } else if (menuItemOption->checkType == QStyleOptionMenuItem::Exclusive) { + checkBoxRect = visualRect(option, checkBoxRect); + + /* + if( useStrongFocus && ( selected || sunken ) ) + { _helper->renderRadioButtonBackground( painter, checkBoxRect, palette.color( QPalette::Window ), outline, sunken ); } + */ + + bool active(menuItemOption->checked); + AnimationMode mode(_animations->widgetStateEngine().isAnimated(widget, AnimationHover) ? AnimationHover : AnimationNone); + qreal opacity(_animations->widgetStateEngine().opacity(widget, AnimationHover)); + + // Style options + styleOptions.setActive(enabled && active); + styleOptions.setAnimationMode(mode); + styleOptions.setOpacity(opacity); + + QColor tickColor = Colors::checkBoxIndicatorColor(styleOptions); + _helper->renderRadioButton(painter, checkBoxRect, indicatorBackground, outline, tickColor, false, enabled && windowActive, active ? RadioOn : RadioOff, _dark, true); + } + + // icon + int iconWidth = 0; + bool showIcon(showIconsInMenuItems()); + if (showIcon) { + iconWidth = isQtQuickControl(option, widget) ? qMax(pixelMetric(PM_SmallIconSize, option, widget), menuItemOption->maxIconWidth) : menuItemOption->maxIconWidth; + } + + QRect iconRect(contentsRect.left(), contentsRect.top() + (contentsRect.height() - iconWidth) / 2, iconWidth, iconWidth); + contentsRect.setLeft(iconRect.right() + Metrics::MenuItem_ItemSpacing + 1); + + if (showIcon && !menuItemOption->icon.isNull()) { + QSize iconSize(pixelMetric(PM_SmallIconSize, option, widget), pixelMetric(PM_SmallIconSize, option, widget)); + iconRect = centerRect(iconRect, iconSize); + iconRect = visualRect(option, iconRect); + + // icon mode + QIcon::Mode mode; + if (selected && !useStrongFocus) { + mode = QIcon::Active; + } else if (selected) { + mode = QIcon::Selected; + } else if (enabled) { + mode = QIcon::Normal; + } else { + mode = QIcon::Disabled; + } + + // icon state + const QIcon::State iconState(sunken ? QIcon::On : QIcon::Off); + QPixmap icon = menuItemOption->icon.pixmap(iconRect.size(), mode, iconState); + painter->drawPixmap(iconRect, icon); + } + + // arrow + QRect arrowRect(contentsRect.right() - Metrics::MenuButton_IndicatorWidth + 1, contentsRect.top() + (contentsRect.height() - Metrics::MenuButton_IndicatorWidth) / 2, Metrics::MenuButton_IndicatorWidth, Metrics::MenuButton_IndicatorWidth); + + if (menuItemOption->menuItemType == QStyleOptionMenuItem::SubMenu) { + // apply right-to-left layout + arrowRect = visualRect(option, arrowRect); + + // arrow orientation + ArrowOrientation orientation(reverseLayout ? ArrowLeft : ArrowRight); + + // color + QColor arrowColor; + if (useStrongFocus && (selected || sunken)) { + arrowColor = palette.color(QPalette::HighlightedText); + } else if (sunken) { + arrowColor = Colors::focusColor(StyleOptions(palette)); + } else if (selected) { + arrowColor = Colors::hoverColor(StyleOptions(palette)); + } else { + styleOptions.setColorRole(QPalette::WindowText); + arrowColor = Colors::arrowOutlineColor(styleOptions); + } + + // render + _helper->renderArrow(painter, arrowRect, arrowColor, orientation); + } + + // text + QRect textRect = contentsRect; + if (!menuItemOption->text.isEmpty()) { + // adjust textRect + QString text = menuItemOption->text; + + textRect = centerRect(textRect, textRect.width(), option->fontMetrics.size(_mnemonics->textFlags(), text).height()); + textRect = visualRect(option, textRect); + + const int arrowWidth = menuItemOption->menuItemType == QStyleOptionMenuItem::SubMenu ? Metrics::MenuButton_IndicatorWidth : 0; + textRect.setRight(textRect.right() - Metrics::MenuItem_MarginWidth - arrowWidth); + + // set font + painter->setFont(menuItemOption->font); + + // color role + const QPalette::ColorRole role = (useStrongFocus && (selected || sunken)) ? QPalette::HighlightedText : QPalette::WindowText; + + // locate accelerator and render + int tabPosition(text.indexOf(QLatin1Char('\t'))); + if (tabPosition >= 0) { + int textFlags(Qt::AlignVCenter | Qt::AlignRight); + QString accelerator(text.mid(tabPosition + 1)); + text = text.left(tabPosition); + QPalette copy(palette); + copy.setColor(QPalette::Active, QPalette::WindowText, Colors::transparentize(copy.color(QPalette::Active, QPalette::WindowText), 0.55)); + copy.setColor(QPalette::Active, QPalette::HighlightedText, Colors::transparentize(copy.color(QPalette::Active, QPalette::HighlightedText), 0.55)); + drawItemText(painter, textRect, textFlags, copy, enabled, accelerator, role); + } + + // render text + int textFlags(Qt::AlignVCenter | (reverseLayout ? Qt::AlignRight : Qt::AlignLeft) | _mnemonics->textFlags()); + textRect = option->fontMetrics.boundingRect(textRect, textFlags, text); + drawItemText(painter, textRect, textFlags, palette, enabled, text, role); + } + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawProgressBarControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + const QStyleOptionProgressBar *progressBarOption(qstyleoption_cast(option)); + if (!progressBarOption) + return true; + + // render groove + QStyleOptionProgressBarV2 progressBarOption2 = *progressBarOption; + progressBarOption2.rect = subElementRect(SE_ProgressBarGroove, progressBarOption, widget); + drawControl(CE_ProgressBarGroove, &progressBarOption2, painter, widget); + + const QObject *styleObject(widget ? widget : progressBarOption->styleObject); + + // enable busy animations + // need to check both widget and passed styleObject, used for QML + if (styleObject && _animations->busyIndicatorEngine().enabled()) { + + // register QML object if defined + if (!widget && progressBarOption->styleObject) { + _animations->busyIndicatorEngine().registerWidget(progressBarOption->styleObject); + } + + _animations->busyIndicatorEngine().setAnimated(styleObject, progressBarOption->maximum == 0 && progressBarOption->minimum == 0); + } + + // check if animated and pass to option + if (_animations->busyIndicatorEngine().isAnimated(styleObject)) { + progressBarOption2.progress = _animations->busyIndicatorEngine().value(); + } + + // render contents + progressBarOption2.rect = subElementRect(SE_ProgressBarContents, progressBarOption, widget); + drawControl(CE_ProgressBarContents, &progressBarOption2, painter, widget); + + // render text + bool textVisible(progressBarOption->textVisible); + bool busy(progressBarOption->minimum == 0 && progressBarOption->maximum == 0); + if (textVisible && !busy) { + progressBarOption2.rect = subElementRect(SE_ProgressBarLabel, progressBarOption, widget); + drawControl(CE_ProgressBarLabel, &progressBarOption2, painter, widget); + } + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawProgressBarContentsControl(const QStyleOption *option, QPainter *painter, const QWidget *) const +{ + const QStyleOptionProgressBar *progressBarOption(qstyleoption_cast(option)); + if (!progressBarOption) + return true; + + // copy rect and palette + QRect rect(option->rect); + const QPalette &palette(option->palette); + + // get direction + const QStyleOptionProgressBarV2 *progressBarOption2(qstyleoption_cast(option)); + bool horizontal = !progressBarOption2 || progressBarOption2->orientation == Qt::Horizontal; + bool inverted(progressBarOption2 ? progressBarOption2->invertedAppearance : false); + bool reverse = horizontal && option->direction == Qt::RightToLeft; + if (inverted) + reverse = !reverse; + + // check if anything is to be drawn + bool busy((progressBarOption->minimum == 0 && progressBarOption->maximum == 0)); + if (busy) { + qreal progress(_animations->busyIndicatorEngine().value()); + + QColor color(palette.color(QPalette::Highlight)); + _helper->renderProgressBarBusyContents(painter, rect, color, _dark ? Colors::darken(color, 0.3) : Colors::darken(color, 0.15), horizontal, reverse, progress); + } else { + QRegion oldClipRegion(painter->clipRegion()); + if (horizontal) { + if (rect.width() < Metrics::ProgressBar_Thickness) { + painter->setClipRect(rect, Qt::IntersectClip); + if (reverse) { + rect.setLeft(rect.left() - Metrics::ProgressBar_Thickness + rect.width()); + } else { + rect.setWidth(Metrics::ProgressBar_Thickness); + } + } + } else { + if (rect.height() < Metrics::ProgressBar_Thickness) { + painter->setClipRect(rect, Qt::IntersectClip); + if (reverse) { + rect.setHeight(Metrics::ProgressBar_Thickness); + } else { + rect.setTop(rect.top() - Metrics::ProgressBar_Thickness + rect.height()); + } + } + } + + _helper->renderProgressBarContents(painter, rect, palette.color(QPalette::Highlight), _dark ? Colors::darken(palette.color(QPalette::Highlight), 0.3) : Colors::darken(palette.color(QPalette::Highlight), 0.15)); + painter->setClipRegion(oldClipRegion); + } + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawProgressBarGrooveControl(const QStyleOption *option, QPainter *painter, const QWidget *) const +{ + const QPalette &palette(option->palette); + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setHasFocus(false); + styleOptions.setSunken(false); + styleOptions.setOpacity(AnimationData::OpacityInvalid); + styleOptions.setAnimationMode(AnimationNone); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + QColor outline(Colors::buttonOutlineColor(styleOptions)); + QColor color(palette.currentColorGroup() ? palette.color(QPalette::Window) : Colors::mix(outline, palette.color(QPalette::Window))); + _helper->renderProgressBarGroove(painter, option->rect, color, outline); + return true; +} + +//___________________________________________________________________________________ +bool Style::drawProgressBarLabelControl(const QStyleOption *option, QPainter *painter, const QWidget *) const +{ + // cast option and check + const QStyleOptionProgressBar *progressBarOption(qstyleoption_cast(option)); + if (!progressBarOption) { + return true; + } + + // get direction and check + const QStyleOptionProgressBarV2 *progressBarOption2(qstyleoption_cast(option)); + bool horizontal = !progressBarOption2 || progressBarOption2->orientation == Qt::Horizontal; + if (!horizontal) { + return true; + } + + // store rect and palette + const QRect &rect(option->rect); + + QPalette palette(option->palette); + palette.setColor(QPalette::WindowText, Colors::transparentize(palette.color(QPalette::Active, QPalette::WindowText), 0.6)); + + // store state and direction + const State &state(option->state); + bool enabled(state & State_Enabled); + + // define text rect + Qt::Alignment hAlign((progressBarOption->textAlignment == Qt::AlignLeft) ? Qt::AlignHCenter : progressBarOption->textAlignment); + drawItemText(painter, rect, Qt::AlignVCenter | hAlign, palette, enabled, progressBarOption->text, QPalette::WindowText); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawScrollBarSliderControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionSlider *sliderOption(qstyleoption_cast(option)); + if (!sliderOption) { + return true; + } + + const State &state(option->state); + bool horizontal(state & State_Horizontal); + + // copy rect and palette + const QRect &rect(horizontal ? option->rect.adjusted(-1, 4, 0, -4) : option->rect.adjusted(4, -1, -4, 0)); + const QPalette &palette(option->palette); + + // define handle rect + QRect handleRect; + + bool enabled(state & State_Enabled); + bool mouseOver((state & State_Active) && enabled && (state & State_MouseOver)); + bool sunken(enabled && (state & (State_On | State_Sunken))); + + // check focus from relevant parent + const QWidget *parent(scrollBarParent(widget)); + bool hasFocus(enabled && parent && parent->hasFocus()); + + // enable animation state + bool handleActive(sliderOption->activeSubControls & SC_ScrollBarSlider); + _animations->scrollBarEngine().updateState(widget, AnimationFocus, hasFocus); + _animations->scrollBarEngine().updateState(widget, AnimationPressed, sunken); + _animations->scrollBarEngine().updateState(widget, AnimationHover, mouseOver); + + AnimationMode mode(_animations->scrollBarEngine().animationMode(widget, SC_ScrollBarSlider)); + qreal opacity(_animations->scrollBarEngine().opacity(widget, SC_ScrollBarSlider)); + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setMouseOver(mouseOver); + styleOptions.setHasFocus(hasFocus); + styleOptions.setSunken(sunken); + styleOptions.setOpacity(opacity); + styleOptions.setAnimationMode(mode); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + QColor color = Colors::scrollBarHandleColor(styleOptions); + if (mouseOver) { + opacity = 1; + } else { + opacity = 0; + } + + /* + if( horizontal ) handleRect = centerRect( rect, rect.width(), rect.height() * (0.5 + 0.5 * opacity)); + else handleRect = centerRect( rect, rect.width() * (0.5 + 0.5 * opacity), rect.height() ); + */ + if (horizontal) { + handleRect = rect.adjusted(0, 6, 0, 2); + handleRect.adjust(0, -6.0 * opacity, 0, -2.0 * opacity); + } else { + handleRect = rect.adjusted(6, 0, 2, 0); + handleRect.adjust(-6.0 * opacity, 0, -2.0 * opacity, 0); + } + + _helper->renderScrollBarHandle(painter, handleRect, color); + return true; +} + +//___________________________________________________________________________________ +bool Style::drawScrollBarAddLineControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // do nothing if no buttons are defined + if (_addLineButtons == NoButton) { + return true; + } + + // cast option and check + const QStyleOptionSlider *sliderOption(qstyleoption_cast(option)); + if (!sliderOption) { + return true; + } + + const State &state(option->state); + bool horizontal(state & State_Horizontal); + bool reverseLayout(option->direction == Qt::RightToLeft); + + // adjust rect, based on number of buttons to be drawn + QRect rect(scrollBarInternalSubControlRect(sliderOption, SC_ScrollBarAddLine)); + + QColor color; + QStyleOptionSlider copy(*sliderOption); + if (_addLineButtons == DoubleButton) { + if (horizontal) { + //Draw the arrows + QSize halfSize(rect.width() / 2, rect.height()); + QRect leftSubButton(rect.topLeft(), halfSize); + QRect rightSubButton(leftSubButton.topRight() + QPoint(1, 0), halfSize); + + copy.rect = leftSubButton; + color = scrollBarArrowColor(©, reverseLayout ? SC_ScrollBarAddLine : SC_ScrollBarSubLine, widget); + _helper->renderArrow(painter, leftSubButton, color, ArrowLeft); + + copy.rect = rightSubButton; + color = scrollBarArrowColor(©, reverseLayout ? SC_ScrollBarSubLine : SC_ScrollBarAddLine, widget); + _helper->renderArrow(painter, rightSubButton, color, ArrowRight); + } else { + QSize halfSize(rect.width(), rect.height() / 2); + QRect topSubButton(rect.topLeft(), halfSize); + QRect botSubButton(topSubButton.bottomLeft() + QPoint(0, 1), halfSize); + + copy.rect = topSubButton; + color = scrollBarArrowColor(©, SC_ScrollBarSubLine, widget); + _helper->renderArrow(painter, topSubButton, color, ArrowUp); + + copy.rect = botSubButton; + color = scrollBarArrowColor(©, SC_ScrollBarAddLine, widget); + _helper->renderArrow(painter, botSubButton, color, ArrowDown); + } + } else if (_addLineButtons == SingleButton) { + copy.rect = rect; + color = scrollBarArrowColor(©, SC_ScrollBarAddLine, widget); + if (horizontal) { + if (reverseLayout) { + _helper->renderArrow(painter, rect, color, ArrowLeft); + } else { + _helper->renderArrow(painter, rect.translated(1, 0), color, ArrowRight); + } + } else { + _helper->renderArrow(painter, rect.translated(0, 1), color, ArrowDown); + } + } + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawScrollBarSubLineControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // do nothing if no buttons are set + if (_subLineButtons == NoButton) { + return true; + } + + // cast option and check + const QStyleOptionSlider *sliderOption(qstyleoption_cast(option)); + if (!sliderOption) { + return true; + } + + const State &state(option->state); + bool horizontal(state & State_Horizontal); + bool reverseLayout(option->direction == Qt::RightToLeft); + + // colors + const QPalette &palette(option->palette); + QColor background(palette.color(QPalette::Window)); + + // adjust rect, based on number of buttons to be drawn + QRect rect(scrollBarInternalSubControlRect(sliderOption, SC_ScrollBarSubLine)); + + QColor color; + QStyleOptionSlider copy(*sliderOption); + if (_subLineButtons == DoubleButton) { + if (horizontal) { + //Draw the arrows + QSize halfSize(rect.width() / 2, rect.height()); + QRect leftSubButton(rect.topLeft(), halfSize); + QRect rightSubButton(leftSubButton.topRight() + QPoint(1, 0), halfSize); + + copy.rect = leftSubButton; + color = scrollBarArrowColor(©, reverseLayout ? SC_ScrollBarAddLine : SC_ScrollBarSubLine, widget); + _helper->renderArrow(painter, leftSubButton, color, ArrowLeft); + + copy.rect = rightSubButton; + color = scrollBarArrowColor(©, reverseLayout ? SC_ScrollBarSubLine : SC_ScrollBarAddLine, widget); + _helper->renderArrow(painter, rightSubButton, color, ArrowRight); + } else { + QSize halfSize(rect.width(), rect.height() / 2); + QRect topSubButton(rect.topLeft(), halfSize); + QRect botSubButton(topSubButton.bottomLeft() + QPoint(0, 1), halfSize); + + copy.rect = topSubButton; + color = scrollBarArrowColor(©, SC_ScrollBarSubLine, widget); + _helper->renderArrow(painter, topSubButton, color, ArrowUp); + + copy.rect = botSubButton; + color = scrollBarArrowColor(©, SC_ScrollBarAddLine, widget); + _helper->renderArrow(painter, botSubButton, color, ArrowDown); + } + + } else if (_subLineButtons == SingleButton) { + copy.rect = rect; + color = scrollBarArrowColor(©, SC_ScrollBarSubLine, widget); + if (horizontal) { + if (reverseLayout) { + _helper->renderArrow(painter, rect.translated(1, 0), color, ArrowRight); + } else { + _helper->renderArrow(painter, rect, color, ArrowLeft); + } + } else { + _helper->renderArrow(painter, rect, color, ArrowUp); + } + } + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawShapedFrameControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionFrameV3 *frameOpt = qstyleoption_cast(option); + if (!frameOpt) { + return false; + } + + switch (frameOpt->frameShape) { + case QFrame::Box: { + if (option->state & State_Sunken) { + return true; + } else { + break; + } + } + case QFrame::HLine: + case QFrame::VLine: { + const QRect &rect(option->rect); + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + QColor color(Colors::separatorColor(styleOptions)); + bool isVertical(frameOpt->frameShape == QFrame::VLine); + _helper->renderSeparator(painter, rect, color, isVertical); + return true; + } + case QFrame::StyledPanel: { + if (isQtQuickControl(option, widget)) { + // ComboBox popup frame + drawFrameMenuPrimitive(option, painter, widget); + return true; + } + + break; + } + + default: + break; + + } + + return false; +} + +//___________________________________________________________________________________ +bool Style::drawRubberBandControl(const QStyleOption *option, QPainter *painter, const QWidget *) const +{ + const QPalette &palette(option->palette); + QRect rect(option->rect); + + QColor color = palette.color(QPalette::Highlight); + painter->setPen(Colors::mix(color, palette.color(QPalette::Active, QPalette::WindowText))); + color.setAlpha(50); + painter->setBrush(color); + painter->setClipRegion(rect); + painter->drawRect(rect.adjusted(0, 0, -1, -1)); + return true; +} + +//___________________________________________________________________________________ +bool Style::drawHeaderSectionControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + const QRect &rect(option->rect); + const QPalette &palette(option->palette); + const State &state(option->state); + bool enabled(state & State_Enabled); + bool mouseOver((state & State_Active) && enabled && (state & State_MouseOver)); + bool sunken(enabled && (state & (State_On | State_Sunken))); + + const QStyleOptionHeader *headerOption(qstyleoption_cast(option)); + if (!headerOption) { + return true; + } + + bool horizontal(headerOption->orientation == Qt::Horizontal); + bool isFirst(horizontal && (headerOption->position == QStyleOptionHeader::Beginning)); + bool isCorner(widget && widget->inherits("QTableCornerButton")); + bool reverseLayout(option->direction == Qt::RightToLeft); + + // update animation state + _animations->headerViewEngine().updateState(widget, rect.topLeft(), mouseOver); + bool animated(enabled && _animations->headerViewEngine().isAnimated(widget, rect.topLeft())); + qreal opacity(_animations->headerViewEngine().opacity(widget, rect.topLeft())); + + QBrush color = palette.base(); + + painter->setRenderHint(QPainter::Antialiasing, false); + painter->setBrush(color); + painter->setPen(Qt::NoPen); + painter->drawRect(rect); + + // outline + painter->setBrush(Qt::NoBrush); + painter->setPen(Colors::alphaColor(palette.color(QPalette::WindowText), 0.2)); + + if (isCorner) { + if (reverseLayout) { + painter->drawPoint(rect.bottomLeft()); + } else { + painter->drawPoint(rect.bottomRight()); + } + } else if (horizontal) { + painter->drawLine(rect.bottomLeft(), rect.bottomRight()); + } else { + if (reverseLayout) { + painter->drawLine(rect.topLeft(), rect.bottomLeft()); + } else { + painter->drawLine(rect.topRight(), rect.bottomRight()); + } + } + + // separators + if (horizontal) { + if (headerOption->section != 0 || isFirst) { + if (reverseLayout) { + painter->drawLine(rect.topLeft(), rect.bottomLeft() - QPoint(0, 1)); + } else { + painter->drawLine(rect.topRight(), rect.bottomRight() - QPoint(0, 1)); + } + } + } else { + if (reverseLayout) { + painter->drawLine(rect.bottomLeft() + QPoint(1, 0), rect.bottomRight()); + } else { + painter->drawLine(rect.bottomLeft(), rect.bottomRight() - QPoint(1, 0)); + } + } + + return true; +} + +bool Style::drawHeaderLabelControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + if (const QStyleOptionHeader *header = qstyleoption_cast(option)) { + QRect rect = header->rect; + if (!header->icon.isNull()) { + QPixmap pixmap = header->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize), (header->state & State_Enabled) ? QIcon::Normal : QIcon::Disabled); + int pixw = pixmap.width(); + + QRect aligned = alignedRect(header->direction, QFlag(header->iconAlignment), pixmap.size(), rect); + QRect inter = aligned.intersected(rect); + painter->drawPixmap(inter.x(), inter.y(), pixmap, inter.x() - aligned.x(), inter.y() - aligned.y(), inter.width(), inter.height()); + + if (header->direction == Qt::LeftToRight) + rect.setLeft(rect.left() + pixw + 2); + else + rect.setRight(rect.right() - pixw - 2); + } + QFont fnt = painter->font(); + fnt.setBold(true); + painter->setFont(fnt); + QPalette palette(header->palette); + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setState(header->state); + + palette.setColor(QPalette::Text, Colors::headerTextColor(styleOptions)); + proxy()->drawItemText(painter, rect, header->textAlignment, palette, (header->state & State_Active), header->text, QPalette::Text); + } + return true; +} + +//___________________________________________________________________________________ +bool Style::drawHeaderEmptyAreaControl(const QStyleOption *option, QPainter *painter, const QWidget *) const +{ + // use the same background as in drawHeaderPrimitive + const QRect &rect(option->rect); + QPalette palette(option->palette); + + bool horizontal(option->state & QStyle::State_Horizontal); + bool reverseLayout(option->direction == Qt::RightToLeft); + + // fill + painter->setRenderHint(QPainter::Antialiasing, false); + painter->setBrush(palette.color(QPalette::Base)); + painter->setPen(Qt::NoPen); + painter->drawRect(rect); + + // outline + painter->setBrush(Qt::NoBrush); + painter->setPen(Colors::alphaColor(palette.color(QPalette::ButtonText), 0.1)); + + if (horizontal) { + painter->drawLine(rect.bottomLeft(), rect.bottomRight()); + } else { + if (reverseLayout) { + painter->drawLine(rect.topLeft(), rect.bottomLeft()); + } else { + painter->drawLine(rect.topRight(), rect.bottomRight()); + } + } + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawTabBarTabLabelControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + if (const QStyleOptionTab *tab = qstyleoption_cast(option)) { + QStyleOptionTabV3 tabV2(*tab); + QRect tr = tabV2.rect; + bool verticalTabs = tabV2.shape == QTabBar::RoundedEast + || tabV2.shape == QTabBar::RoundedWest + || tabV2.shape == QTabBar::TriangularEast + || tabV2.shape == QTabBar::TriangularWest; + + int alignment = Qt::AlignCenter | Qt::TextShowMnemonic; + if (!proxy()->styleHint(SH_UnderlineShortcut, option, widget)) + alignment |= Qt::TextHideMnemonic; + + if (verticalTabs) { + painter->save(); + int newX, newY, newRot; + if (tabV2.shape == QTabBar::RoundedEast || tabV2.shape == QTabBar::TriangularEast) { + newX = tr.width() + tr.x(); + newY = tr.y(); + newRot = 90; + } else { + newX = tr.x(); + newY = tr.y() + tr.height(); + newRot = -90; + } + QTransform m = QTransform::fromTranslate(newX, newY); + m.rotate(newRot); + painter->setTransform(m, true); + } + QRect iconRect; + tabLayout(&tabV2, widget, &tr, &iconRect, proxy()); + tr = proxy()->subElementRect(SE_TabBarTabText, option, widget); //we compute tr twice because the style may override subElementRect + + if (!tabV2.icon.isNull()) { + QPixmap tabIcon = tabV2.icon.pixmap(tabV2.iconSize, (tabV2.state & State_Enabled) ? QIcon::Normal : QIcon::Disabled, + (tabV2.state & State_Selected) ? QIcon::On : QIcon::Off); + painter->drawPixmap(iconRect.x(), iconRect.y(), tabIcon); + } + + QFont font = painter->font(); + font.setBold(true); + painter->setFont(font); + if (!(tabV2.state & State_Enabled)) { + if (tabV2.state & State_Selected) { + painter->setPen(Colors::mix(option->palette.brush(QPalette::Text).color(), option->palette.brush(QPalette::Window).color(), 0.3)); + } else { + painter->setPen(Colors::mix(option->palette.brush(QPalette::Text).color(), option->palette.brush(QPalette::Window).color(), 0.4)); + } + } else { + if (tabV2.state & State_Selected) { + painter->setPen(option->palette.brush(QPalette::WindowText).color()); + } else if (tabV2.state & State_Active && tabV2.state & State_MouseOver) { + painter->setPen(Colors::mix(option->palette.brush(QPalette::Dark).color(), option->palette.brush(QPalette::Text).color(), 0.7)); + } else { + painter->setPen(Colors::mix(option->palette.brush(QPalette::Dark).color(), option->palette.brush(QPalette::Text).color(), 0.6)); + } + } + + proxy()->drawItemText(painter, tr, alignment, tab->palette, tab->state & State_Enabled, tab->text, QPalette::NoRole); + + if (verticalTabs) { + painter->restore(); + } + + if (tabV2.state & State_HasFocus) { + int OFFSET = 1 + pixelMetric(PM_DefaultFrameWidth); + + int x1, x2; + x1 = tabV2.rect.left(); + x2 = tabV2.rect.right() - 1; + + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*tab); + fropt.rect.setRect(x1 + 1 + OFFSET, tabV2.rect.y() + OFFSET, + x2 - x1 - 2 * OFFSET, tabV2.rect.height() - 2 * OFFSET); + drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget); + } + } + + // store rect and palette + const QRect &rect(option->rect); + const QPalette &palette(option->palette); + + // check focus + const State &state(option->state); + bool enabled(state & State_Enabled); + bool selected(state & State_Selected); + bool hasFocus(enabled && selected && (state & State_HasFocus)); + + // update mouse over animation state + _animations->tabBarEngine().updateState(widget, rect.topLeft(), AnimationFocus, hasFocus); + bool animated(enabled && selected && _animations->tabBarEngine().isAnimated(widget, rect.topLeft(), AnimationFocus)); + qreal opacity(_animations->tabBarEngine().opacity(widget, rect.topLeft(), AnimationFocus)); + + if (!(hasFocus || animated)) + return true; + + // code is copied from QCommonStyle, but adds focus + // cast option and check + const QStyleOptionTab *tabOption(qstyleoption_cast(option)); + if (!tabOption || tabOption->text.isEmpty()) { + return true; + } + + // tab option rect + bool verticalTabs(isVerticalTab(tabOption)); + int textFlags(Qt::AlignCenter | _mnemonics->textFlags()); + + // text rect + QRect textRect(subElementRect(SE_TabBarTabText, option, widget)); + + if (verticalTabs) { + // properly rotate painter + painter->save(); + int newX, newY, newRot; + if (tabOption->shape == QTabBar::RoundedEast || tabOption->shape == QTabBar::TriangularEast) { + newX = rect.width() + rect.x(); + newY = rect.y(); + newRot = 90; + } else { + newX = rect.x(); + newY = rect.y() + rect.height(); + newRot = -90; + } + + QTransform transform; + transform.translate(newX, newY); + transform.rotate(newRot); + painter->setTransform(transform, true); + } + + // adjust text rect based on font metrics + textRect = option->fontMetrics.boundingRect(textRect, textFlags, tabOption->text); + + if (verticalTabs) { + painter->restore(); + } + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawTabBarTabShapeControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + const QStyleOptionTab *tabOption(qstyleoption_cast(option)); + if (!tabOption) { + return true; + } + + // palette and state + const QPalette &palette(option->palette); + const State &state(option->state); + bool enabled(state & State_Enabled); + bool selected(state & State_Selected); + bool mouseOver((state & State_Active) && !selected && (state & State_MouseOver) && enabled); + + // check if tab is being dragged + bool isDragged(widget && selected && painter->device() != widget); + bool isLocked(widget && _tabBarData->isLocked(widget)); + + // store rect + QRect rect(option->rect); + + // update mouse over animation state + _animations->tabBarEngine().updateState(widget, rect.topLeft(), AnimationHover, mouseOver); + bool animated(enabled && !selected && _animations->tabBarEngine().isAnimated(widget, rect.topLeft(), AnimationHover)); + qreal opacity(_animations->tabBarEngine().opacity(widget, rect.topLeft(), AnimationHover)); + + // lock state + if (selected && widget && isDragged) { + _tabBarData->lock(widget); + } else if (widget && selected && _tabBarData->isLocked(widget)) { + _tabBarData->release(); + } + + // tab position + const QStyleOptionTab::TabPosition &position = tabOption->position; + bool isSingle(position == QStyleOptionTab::OnlyOneTab); + bool isQtQuickControl(this->isQtQuickControl(option, widget)); + bool isFirst(isSingle || position == QStyleOptionTab::Beginning); + bool isLast(isSingle || position == QStyleOptionTab::End); + bool isLeftOfSelected(!isLocked && tabOption->selectedPosition == QStyleOptionTab::NextIsSelected); + bool isRightOfSelected(!isLocked && tabOption->selectedPosition == QStyleOptionTab::PreviousIsSelected); + + // true if widget is aligned to the frame + // need to check for 'isRightOfSelected' because for some reason the isFirst flag is set when active tab is being moved + isFirst &= !isRightOfSelected; + isLast &= !isLeftOfSelected; + + // swap state based on reverse layout, so that they become layout independent + bool reverseLayout(option->direction == Qt::RightToLeft); + bool verticalTabs(isVerticalTab(tabOption)); + if (reverseLayout && !verticalTabs) { + qSwap(isFirst, isLast); + qSwap(isLeftOfSelected, isRightOfSelected); + } + + // overlap + // for QtQuickControls, ovelap is already accounted of in the option. Unlike in the qwidget case + int overlap(isQtQuickControl ? 0 : Metrics::TabBar_TabOverlap); + + // adjust rect and define corners based on tabbar orientation + Corners corners; + switch (tabOption->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + corners = CornersTop; + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + corners = CornersBottom; + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + corners = CornersLeft; + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + corners = CornersRight; + break; + default: + break; + } + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setState(option->state); + + // underline + QColor underline(enabled && selected ? Colors::focusColor(StyleOptions(palette)) : selected || mouseOver ? option->palette.color(QPalette::Window).darker() : Qt::transparent); + + // outline + QColor outline = QColor(); + if (selected && widget && widget->property("movable").toBool()) { + outline = Colors::frameOutlineColor(StyleOptions(palette)); + } + + // background + QColor background = Colors::tabBarColor(styleOptions); + + // render + QRegion oldRegion(painter->clipRegion()); + painter->setClipRect(option->rect, Qt::IntersectClip); + _helper->renderTabBarTab(painter, rect, background, underline, outline, corners, widget && widget->property("movable").toBool()); + painter->setClipRegion(oldRegion); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawToolBoxTabLabelControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // rendering is similar to drawPushButtonLabelControl + // cast option and check + const QStyleOptionToolBox *toolBoxOption(qstyleoption_cast(option)); + if (!toolBoxOption) { + return true; + } + + // copy palette + const QPalette &palette(option->palette); + + const State &state(option->state); + bool enabled(state & State_Enabled); + + // text alignment + int textFlags(_mnemonics->textFlags() | Qt::AlignCenter); + + // contents rect + QRect rect(subElementRect(SE_ToolBoxTabContents, option, widget)); + + // store icon size + int iconSize(pixelMetric(QStyle::PM_SmallIconSize, option, widget)); + + // find contents size and rect + QRect contentsRect(rect); + QSize contentsSize; + if (!toolBoxOption->text.isEmpty()) { + contentsSize = option->fontMetrics.size(_mnemonics->textFlags(), toolBoxOption->text); + if (!toolBoxOption->icon.isNull()) { + contentsSize.rwidth() += Metrics::ToolBox_TabItemSpacing; + } + } + + // icon size + if (!toolBoxOption->icon.isNull()) { + contentsSize.setHeight(qMax(contentsSize.height(), iconSize)); + contentsSize.rwidth() += iconSize; + } + + // adjust contents rect + contentsRect = centerRect(contentsRect, contentsSize); + + // render icon + if (!toolBoxOption->icon.isNull()) { + // icon rect + QRect iconRect; + if (toolBoxOption->text.isEmpty()) { + iconRect = centerRect(contentsRect, iconSize, iconSize); + } else { + iconRect = contentsRect; + iconRect.setWidth(iconSize); + iconRect = centerRect(iconRect, iconSize, iconSize); + contentsRect.setLeft(iconRect.right() + Metrics::ToolBox_TabItemSpacing + 1); + } + + iconRect = visualRect(option, iconRect); + const QIcon::Mode mode(enabled ? QIcon::Normal : QIcon::Disabled); + QPixmap pixmap(toolBoxOption->icon.pixmap(iconSize, mode)); + drawItemPixmap(painter, iconRect, textFlags, pixmap); + } + + // render text + if (!toolBoxOption->text.isEmpty()) { + contentsRect = visualRect(option, contentsRect); + drawItemText(painter, contentsRect, textFlags, palette, enabled, toolBoxOption->text, QPalette::WindowText); + } + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawToolBoxTabShapeControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionToolBox *toolBoxOption(qstyleoption_cast(option)); + if (!toolBoxOption) { + return true; + } + + // copy rect and palette + const QRect &rect(option->rect); + QRect tabRect(toolBoxTabContentsRect(option, widget)); + + /* + * important: option returns the wrong palette. + * we use the widget palette instead, when set + */ + QPalette palette(widget ? widget->palette() : option->palette); + + // store flags + const State &flags(option->state); + bool enabled(flags & State_Enabled); + bool selected(flags & State_Selected); + bool mouseOver((flags & State_Active) && enabled && !selected && (flags & State_MouseOver)); + + // update animation state + /* + * the proper widget ( the toolbox tab ) is not passed as argument by Qt. + * What is passed is the toolbox directly. To implement animations properly, + *the painter->device() is used instead + */ + bool isAnimated(false); + qreal opacity(AnimationData::OpacityInvalid); + QPaintDevice *device = painter->device(); + if (enabled && device) { + _animations->toolBoxEngine().updateState(device, mouseOver); + isAnimated = _animations->toolBoxEngine().isAnimated(device); + opacity = _animations->toolBoxEngine().opacity(device); + } + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setMouseOver(mouseOver); + styleOptions.setHasFocus(false); + styleOptions.setOpacity(opacity); + styleOptions.setAnimationMode(isAnimated ? AnimationHover : AnimationNone); + + // color + QColor outline; + if (selected) { + outline = Colors::focusColor(StyleOptions(palette)); + } else { + outline = Colors::frameOutlineColor(styleOptions); + } + + // render + _helper->renderToolBoxFrame(painter, rect, tabRect.width(), outline); + + return true; +} + +//___________________________________________________________________________________ +bool Style::drawDockWidgetTitleControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionDockWidget *dockWidgetOption = ::qstyleoption_cast(option); + if (!dockWidgetOption) { + return true; + } + + const QPalette &palette(option->palette); + const State &state(option->state); + bool enabled(state & State_Enabled); + bool reverseLayout(option->direction == Qt::RightToLeft); + + // cast to v2 to check vertical bar + const QStyleOptionDockWidgetV2 *v2 = qstyleoption_cast(option); + bool verticalTitleBar(v2 ? v2->verticalTitleBar : false); + + QRect buttonRect(subElementRect(dockWidgetOption->floatable ? SE_DockWidgetFloatButton : SE_DockWidgetCloseButton, option, widget)); + + // get rectangle and adjust to properly accounts for buttons + QRect rect(insideMargin(dockWidgetOption->rect, Metrics::Frame_FrameWidth)); + if (verticalTitleBar) { + if (buttonRect.isValid()) { + rect.setTop(buttonRect.bottom() + 1); + } + } else if (reverseLayout) { + if (buttonRect.isValid()) { + rect.setLeft(buttonRect.right() + 1); + } + rect.adjust(0, 0, -4, 0); + } else { + if (buttonRect.isValid()) { + rect.setRight(buttonRect.left() - 1); + } + rect.adjust(4, 0, 0, 0); + } + + QString title(dockWidgetOption->title); + int titleWidth = dockWidgetOption->fontMetrics.size(_mnemonics->textFlags(), title).width(); + int width = verticalTitleBar ? rect.height() : rect.width(); + if (width < titleWidth) { + title = dockWidgetOption->fontMetrics.elidedText(title, Qt::ElideMiddle, width, Qt::TextShowMnemonic); + } + + if (verticalTitleBar) { + QSize size = rect.size(); + size.transpose(); + rect.setSize(size); + + painter->save(); + painter->translate(rect.left(), rect.top() + rect.width()); + painter->rotate(-90); + painter->translate(-rect.left(), -rect.top()); + drawItemText(painter, rect, Qt::AlignLeft | Qt::AlignVCenter | _mnemonics->textFlags(), palette, enabled, title, QPalette::WindowText); + painter->restore(); + } else { + drawItemText(painter, rect, Qt::AlignLeft | Qt::AlignVCenter | _mnemonics->textFlags(), palette, enabled, title, QPalette::WindowText); + } + + return true; +} + +//______________________________________________________________ +bool Style::drawGroupBoxComplexControl(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const +{ + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast(option)) { + painter->save(); + + QRect textRect = proxy()->subControlRect(CC_GroupBox, groupBox, SC_GroupBoxLabel, widget); + QRect checkBoxRect = proxy()->subControlRect(CC_GroupBox, groupBox, SC_GroupBoxCheckBox, widget); + + // Draw title + if ((groupBox->subControls & QStyle::SC_GroupBoxLabel) && !groupBox->text.isEmpty()) { + QColor textColor = groupBox->textColor; + if (textColor.isValid()) { + painter->setPen(textColor); + } + int alignment = int(groupBox->textAlignment); + if (!styleHint(QStyle::SH_UnderlineShortcut, option, widget)) { + alignment |= Qt::TextHideMnemonic; + } + QFont font = painter->font(); + font.setBold(true); + painter->setFont(font); + painter->drawText(textRect, Qt::TextShowMnemonic | Qt::AlignLeft | Qt::AlignVCenter | alignment, groupBox->text); + } + if (groupBox->subControls & SC_GroupBoxCheckBox) { + QStyleOptionButton box; + box.QStyleOption::operator=(*groupBox); + box.rect = checkBoxRect; + proxy()->drawPrimitive(PE_IndicatorCheckBox, &box, painter, widget); + } + painter->restore(); + } + + return true; +} + +//______________________________________________________________ +bool Style::drawToolButtonComplexControl(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionToolButton *toolButtonOption(qstyleoption_cast(option)); + if (!toolButtonOption) { + return true; + } + + // need to alter palette for focused buttons + const State &state(option->state); + bool enabled(state & State_Enabled); + bool mouseOver((state & State_Active) && enabled && (option->state & State_MouseOver)); + bool hasFocus(enabled && (option->state & State_HasFocus)); + bool sunken(state & (State_On | State_Sunken)); + bool flat(state & State_AutoRaise); + + // update animation state + // pressed takes precedence over mouse + + qreal mouseOpacity = 0.0; + qreal pressedOpacity = 0.0; + + _animations->widgetStateEngine().updateState(widget, AnimationPressed, sunken); + _animations->widgetStateEngine().updateState(widget, AnimationHover, mouseOver); + + AnimationMode mode(_animations->widgetStateEngine().buttonAnimationMode(widget)); + + if (mode == AnimationPressed) { + pressedOpacity = _animations->widgetStateEngine().buttonOpacity(widget); + } else if (sunken) { + pressedOpacity = 1.0; + } else if (mode == AnimationHover) { + mouseOpacity = _animations->widgetStateEngine().buttonOpacity(widget); + } else if (mouseOver) { + mouseOpacity = 1.0; + } + + // detect buttons in tabbar, for which special rendering is needed + bool isDockWidgetTitleButton(widget && widget->inherits("QDockWidgetTitleButton")); + bool inTabBar(widget && qobject_cast(widget->parentWidget())); + bool isMenuTitle(this->isMenuTitle(widget)); + if (isMenuTitle) { + // copy option to adust state, and set font as not-bold + QStyleOptionToolButton copy(*toolButtonOption); + copy.font.setBold(false); + copy.state = State_Enabled; + + // render + renderMenuTitle(©, painter, widget); + return true; + } + + // copy option and alter palette + QStyleOptionToolButton copy(*toolButtonOption); + + if (isDockWidgetTitleButton) { + // cast to abstract button + // adjust state to have correct icon rendered + const QAbstractButton *button(qobject_cast(widget)); + if (button->isChecked() || button->isDown()) { + copy.state |= State_Enabled | State_On | State_Sunken; + } + if (button->underMouse()) { + copy.state |= State_Enabled | State_MouseOver | State_Active; + } + + } + + bool hasPopupMenu(toolButtonOption->features & QStyleOptionToolButton::MenuButtonPopup); + const bool hasInlineIndicator( + toolButtonOption->features & QStyleOptionToolButton::HasMenu + && toolButtonOption->features & QStyleOptionToolButton::PopupDelay + && !hasPopupMenu); + + QRect buttonRect(subControlRect(CC_ToolButton, option, SC_ToolButton, widget)); + QRect menuRect(subControlRect(CC_ToolButton, option, SC_ToolButtonMenu, widget)); + + // frame + if (toolButtonOption->subControls & SC_ToolButton || isDockWidgetTitleButton) { + copy.rect = buttonRect; + if (inTabBar) { + QRect rect(option->rect); + QColor background(Colors::mix(option->palette.window().color(), option->palette.shadow().color(), 0.15)); + background = Colors::mix(background, Qt::white, 0.2 * mouseOpacity); + background = Colors::mix(background, Qt::black, 0.15 * pressedOpacity); + QColor outline(Colors::frameOutlineColor(StyleOptions(option->palette))); + painter->setPen(background); + painter->setBrush(background); + switch (toolButtonOption->arrowType) { + case Qt::UpArrow: + painter->drawRect(rect.adjusted(1, 1, -2, -1)); + break; + case Qt::DownArrow: + painter->drawRect(rect.adjusted(1, 0, -2, -2)); + break; + case Qt::LeftArrow: + painter->drawRect(rect.adjusted(1, 1, -1, -2)); + break; + case Qt::RightArrow: + painter->drawRect(rect.adjusted(0, 1, -2, -2)); + break; + } + painter->setPen(outline); + switch (toolButtonOption->arrowType) { + case Qt::DownArrow: + painter->drawLine(rect.bottomLeft(), rect.bottomRight()); + break; + case Qt::RightArrow: + painter->drawLine(rect.topRight(), rect.bottomRight()); + break; + } + switch (toolButtonOption->arrowType) { + case Qt::UpArrow: + case Qt::DownArrow: + painter->drawLine(rect.topLeft(), rect.bottomLeft()); + painter->drawLine(rect.topLeft(), rect.bottomLeft()); + break; + case Qt::LeftArrow: + case Qt::RightArrow: + painter->drawLine(rect.topLeft(), rect.topRight()); + painter->drawLine(rect.bottomLeft(), rect.bottomRight()); + break; + } + } else if (sunken && hasPopupMenu && !(toolButtonOption->activeSubControls & SC_ToolButton)) { + // Only menu button is active. so draw left hand side od button raised + QStyleOptionToolButton btn(copy); + btn.state |= State_Raised; + btn.state &= ~State_Sunken; + btn.state &= ~State_AutoRaise; + drawPrimitive(PE_PanelButtonTool, &btn, painter, widget); + } else { + drawPrimitive(PE_PanelButtonTool, ©, painter, widget); + } + } + + // arrow + if (hasPopupMenu) { + copy.rect = menuRect; + if (!flat || mouseOver || sunken) { + drawPrimitive(PE_IndicatorButtonDropDown, ©, painter, widget); + } + + drawPrimitive(PE_IndicatorArrowDown, ©, painter, widget); + } else if (hasInlineIndicator) { + copy.rect = menuRect; + + drawPrimitive(PE_IndicatorArrowDown, ©, painter, widget); + } + + // contents + { + // restore state + copy.state = state; + + // define contents rect + QRect contentsRect(buttonRect); + + // detect dock widget title button + // for dockwidget title buttons, do not take out margins, so that icon do not get scaled down + if (isDockWidgetTitleButton) { + // cast to abstract button + // adjust state to have correct icon rendered + const QAbstractButton *button(qobject_cast(widget)); + if (button->isChecked() || button->isDown()) { + copy.state |= State_Enabled | State_On | State_Sunken; + } + if (button->underMouse()) { + copy.state |= State_Enabled | State_MouseOver | State_Active; + } + } else if (!inTabBar && hasInlineIndicator) { + int marginWidth(flat ? Metrics::ToolButton_MarginWidth : Metrics::Button_MarginWidth + Metrics::Frame_FrameWidth); + contentsRect = insideMargin(contentsRect, marginWidth, 0); + contentsRect.setRight(contentsRect.right() - Metrics::ToolButton_InlineIndicatorWidth); + contentsRect = visualRect(option, contentsRect); + } + + copy.rect = contentsRect; + + // render + drawControl(CE_ToolButtonLabel, ©, painter, widget); + } + + return true; +} + +//______________________________________________________________ +bool Style::drawComboBoxComplexControl(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionComboBox *comboBoxOption(qstyleoption_cast(option)); + if (!comboBoxOption) { + return true; + } + + // rect and palette + const QRect &rect(option->rect); + const QPalette &palette(option->palette); + + // state + const State &state(option->state); + bool enabled(state & State_Enabled); + bool windowActive(state & State_Active); + bool editable(comboBoxOption->editable); + bool arrowActive(comboBoxOption->activeSubControls & SC_ComboBoxArrow); + bool flat(!comboBoxOption->frame); + bool mouseOver; + bool hasFocus; + bool sunken; + if (editable) { + mouseOver = windowActive && arrowActive && enabled && (state & State_MouseOver); + hasFocus = enabled && (state & (State_HasFocus | State_Sunken)); + sunken = arrowActive && enabled && (state & (State_On | State_Sunken)); + } else { + mouseOver = windowActive && enabled && (state & State_MouseOver); + hasFocus = enabled && (state & (State_HasFocus | State_Sunken)); + sunken = enabled && (state & (State_On | State_Sunken)); + } + + // update animation state + // sunken takes precedence over hover that takes precedence over focus + _animations->inputWidgetEngine().updateState(widget, AnimationPressed, sunken); + _animations->inputWidgetEngine().updateState(widget, AnimationHover, mouseOver); + _animations->inputWidgetEngine().updateState(widget, AnimationFocus, hasFocus && !mouseOver); + + // Style options + StyleOptions styleOptions(option->palette); + styleOptions.setMouseOver(mouseOver); + styleOptions.setHasFocus(hasFocus); + styleOptions.setSunken(sunken); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + // frame + if (option->subControls & SC_ComboBoxFrame) { + if (editable) { + flat |= (rect.height() <= 2 * Metrics::Frame_FrameWidth + Metrics::MenuButton_IndicatorWidth); + if (flat) { + QColor background(palette.color(QPalette::Base)); + + painter->setBrush(background); + painter->setPen(Qt::NoPen); + painter->drawRect(rect); + } else { + AnimationMode mode(_animations->inputWidgetEngine().buttonAnimationMode(widget)); + qreal opacity(_animations->inputWidgetEngine().buttonOpacity(widget)); + + // Style options + styleOptions.setAnimationMode(mode); + styleOptions.setOpacity(opacity); + + // define colors + QColor shadow(Colors::shadowColor(styleOptions)); + QColor outline(Colors::buttonOutlineColor(styleOptions)); + QColor background(Colors::buttonBackgroundColor(styleOptions)); + + // render + _helper->renderButtonFrame(painter, rect, background, outline, shadow, hasFocus, sunken, mouseOver, enabled && windowActive, _dark); + + QStyleOptionComplex tmpOpt(*option); + tmpOpt.rect.setWidth(tmpOpt.rect.width() - subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget).width() + 3); + + drawPrimitive(PE_FrameLineEdit, &tmpOpt, painter, widget); + } + } else { + AnimationMode mode(_animations->inputWidgetEngine().buttonAnimationMode(widget)); + qreal opacity(_animations->inputWidgetEngine().buttonOpacity(widget)); + + // Style options + styleOptions.setAnimationMode(mode); + styleOptions.setOpacity(opacity); + + if (flat) { + // define colors and render + QColor color(Colors::toolButtonColor(styleOptions)); + _helper->renderToolButtonFrame(painter, rect, color, sunken); + } else { + // define colors + QColor shadow(Colors::shadowColor(styleOptions)); + QColor outline(Colors::buttonOutlineColor(styleOptions)); + QColor background(Colors::buttonBackgroundColor(styleOptions)); + + // render + _helper->renderButtonFrame(painter, rect, background, outline, shadow, hasFocus, sunken, mouseOver, enabled && windowActive, _dark); + + if (hasFocus) { + QStyleOption copy(*option); + copy.rect.adjust(4, 4, -4, -4); + drawPrimitive(PE_FrameFocusRect, ©, painter, widget); + } + } + } + } + + // arrow + if (option->subControls & SC_ComboBoxArrow) { + // detect empty comboboxes + const QComboBox *comboBox = qobject_cast(widget); + bool empty(comboBox && !comboBox->count()); + + // Style options + styleOptions.setColorRole(QPalette::ButtonText); + + // arrow color + QColor arrowColor = Colors::arrowOutlineColor(styleOptions); + + // arrow rect + QRect arrowRect(subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget)); + + // render + _helper->renderArrow(painter, arrowRect, arrowColor, ArrowDown); + } + + return true; +} + +//______________________________________________________________ +bool Style::drawSpinBoxComplexControl(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const +{ + const QStyleOptionSpinBox *spinBoxOption(qstyleoption_cast(option)); + if (!spinBoxOption) { + return true; + } + + // store palette and rect + const QPalette &palette(option->palette); + const QRect &rect(option->rect); + + if (option->subControls & SC_SpinBoxFrame) { + // detect flat spinboxes + bool flat(!spinBoxOption->frame); + flat |= (rect.height() < 2 * Metrics::Frame_FrameWidth + Metrics::SpinBox_ArrowButtonWidth); + if (flat) { + QColor background(palette.color(QPalette::Base)); + + painter->setBrush(background); + painter->setPen(Qt::NoPen); + painter->drawRect(rect); + } else { + drawPrimitive(PE_FrameLineEdit, option, painter, widget); + } + } + + if (option->subControls & SC_SpinBoxUp) { + renderSpinBoxArrow(SC_SpinBoxUp, spinBoxOption, painter, widget); + } + if (option->subControls & SC_SpinBoxDown) { + renderSpinBoxArrow(SC_SpinBoxDown, spinBoxOption, painter, widget); + } + + return true; +} + +//______________________________________________________________ +bool Style::drawSliderComplexControl(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionSlider *sliderOption(qstyleoption_cast(option)); + if (!sliderOption) { + return true; + } + + // copy rect and palette + const QRect &rect(option->rect); + const QPalette &palette(option->palette); + + // copy state + const State &state(option->state); + bool enabled(state & State_Enabled); + bool windowActive(state & State_Active); + bool mouseOver((state & State_Active) && enabled && (state & State_MouseOver)); + bool hasFocus(enabled && (state & State_HasFocus)); + bool horizontal(sliderOption->orientation == Qt::Horizontal); + Side tickSide { SideNone }; + if (horizontal && sliderOption->tickPosition == QSlider::TicksAbove) { + tickSide = (Side)((int) tickSide | (int) SideTop); + } + if (horizontal && sliderOption->tickPosition == QSlider::TicksBelow) { + tickSide = (Side)((int) tickSide | (int) SideBottom); + } + if (!horizontal && sliderOption->tickPosition == QSlider::TicksLeft) { + tickSide = (Side)((int) tickSide | (int) SideLeft); + } + if (!horizontal && sliderOption->tickPosition == QSlider::TicksRight) { + tickSide = (Side)((int) tickSide | (int) SideRight); + } + + // tickmarks + if (Adwaita::Config::SliderDrawTickMarks && (sliderOption->subControls & SC_SliderTickmarks)) { + bool upsideDown(sliderOption->upsideDown); + int tickPosition(sliderOption->tickPosition); + int available(pixelMetric(PM_SliderSpaceAvailable, option, widget)); + int interval = sliderOption->tickInterval; + if (interval < 1) { + interval = sliderOption->pageStep; + } + if (interval >= 1) { + int fudge(pixelMetric(PM_SliderLength, option, widget) / 2); + int current(sliderOption->minimum); + + // store tick lines + QRect grooveRect(subControlRect(CC_Slider, sliderOption, SC_SliderGroove, widget)); + QList tickLines; + if (horizontal) { + if (tickPosition & QSlider::TicksAbove) { + tickLines.append(QLine(rect.left(), grooveRect.top() - Metrics::Slider_TickMarginWidth, rect.left(), grooveRect.top() - Metrics::Slider_TickMarginWidth - Metrics::Slider_TickLength)); + } + if (tickPosition & QSlider::TicksBelow) { + tickLines.append(QLine(rect.left(), grooveRect.bottom() + Metrics::Slider_TickMarginWidth, rect.left(), grooveRect.bottom() + Metrics::Slider_TickMarginWidth + Metrics::Slider_TickLength)); + } + } else { + if (tickPosition & QSlider::TicksAbove) { + tickLines.append(QLine(grooveRect.left() - Metrics::Slider_TickMarginWidth, rect.top(), grooveRect.left() - Metrics::Slider_TickMarginWidth - Metrics::Slider_TickLength, rect.top())); + } + if (tickPosition & QSlider::TicksBelow) { + tickLines.append(QLine(grooveRect.right() + Metrics::Slider_TickMarginWidth, rect.top(), grooveRect.right() + Metrics::Slider_TickMarginWidth + Metrics::Slider_TickLength, rect.top())); + } + } + + // Style options + StyleOptions styleOptions(palette); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + // colors + QColor base(Colors::separatorColor(styleOptions)); + + while (current <= sliderOption->maximum) { + // adjust color + QColor color(base); + painter->setPen(color); + + // calculate positions and draw lines + int position(sliderPositionFromValue(sliderOption->minimum, sliderOption->maximum, current, available) + fudge); + foreach (const QLine &tickLine, tickLines) { + if (horizontal) { + painter->drawLine(tickLine.translated(upsideDown ? (rect.width() - position) : position, 0)); + } else { + painter->drawLine(tickLine.translated(0, upsideDown ? (rect.height() - position) : position)); + } + } + + // go to next position + current += interval; + } + } + } + + // groove + if (sliderOption->subControls & SC_SliderGroove) { + if (hasFocus) { + QRect focusRect = proxy()->subElementRect(SE_SliderFocusRect, option, widget); + + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*option); + fropt.rect = focusRect; + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget); + } + + // retrieve groove rect + QRect grooveRect(subControlRect(CC_Slider, sliderOption, SC_SliderGroove, widget)); + + // Style options + StyleOptions styleOptions(palette); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + // base color + QColor outline(Colors::buttonOutlineColor(styleOptions)); + QColor grooveColor(palette.currentColorGroup() ? palette.color(QPalette::Window) : Colors::mix(outline, palette.color(QPalette::Window))); + QColor highlightColor(palette.color(QPalette::Highlight)); + QColor highlightOutline(_dark ? Colors::darken(highlightColor, 0.3) : Colors::darken(highlightColor, 0.15)); + + if (!enabled) { + _helper->renderProgressBarGroove(painter, grooveRect, grooveColor, outline); + } else { + bool upsideDown(sliderOption->upsideDown); + + // handle rect + QRect handleRect(subControlRect(CC_Slider, sliderOption, SC_SliderHandle, widget)); + + if (sliderOption->orientation == Qt::Horizontal) { + QRect leftRect(grooveRect); + QRect rightRect(grooveRect); + leftRect.setRight(handleRect.right() - Metrics::Slider_ControlThickness / 2); + rightRect.setLeft(handleRect.left() + Metrics::Slider_ControlThickness / 2); + + if (upsideDown) { + _helper->renderProgressBarGroove(painter, leftRect, grooveColor, outline); + _helper->renderProgressBarContents(painter, rightRect, highlightColor, highlightOutline); + } else { + _helper->renderProgressBarContents(painter, leftRect, highlightColor, highlightOutline); + _helper->renderProgressBarGroove(painter, rightRect, grooveColor, outline); + } + } else { + QRect topRect(grooveRect); + topRect.setBottom(handleRect.bottom() - Metrics::Slider_ControlThickness / 2); + QRect bottomRect(grooveRect); + bottomRect.setTop(handleRect.top() + Metrics::Slider_ControlThickness / 2); + + if (upsideDown) { + _helper->renderProgressBarGroove(painter, topRect, grooveColor, outline); + _helper->renderProgressBarContents(painter, bottomRect, highlightColor, highlightOutline); + } else { + _helper->renderProgressBarContents(painter, topRect, highlightColor, highlightOutline); + _helper->renderProgressBarGroove(painter, bottomRect, grooveColor, outline); + } + } + } + } + + // handle + if (sliderOption->subControls & SC_SliderHandle) { + // get rect and center + QRect handleRect(subControlRect(CC_Slider, sliderOption, SC_SliderHandle, widget)); + + // handle state + bool handleActive(sliderOption->activeSubControls & SC_SliderHandle); + bool sunken(state & (State_On | State_Sunken)); + + // animation state + _animations->widgetStateEngine().updateState(widget, AnimationHover, handleActive && mouseOver); + _animations->widgetStateEngine().updateState(widget, AnimationFocus, hasFocus); + AnimationMode mode(_animations->widgetStateEngine().buttonAnimationMode(widget)); + qreal opacity(_animations->widgetStateEngine().buttonOpacity(widget)); + + // Style options + StyleOptions styleOptions(palette); + styleOptions.setMouseOver(mouseOver); + styleOptions.setOpacity(opacity); + styleOptions.setCheckboxState(CheckOff); + styleOptions.setInMenu(true); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + // define colors + QColor background(Colors::indicatorBackgroundColor(styleOptions)); + + styleOptions.setMouseOver(handleActive && mouseOver); + + QColor outline(Colors::indicatorOutlineColor(styleOptions)); + QColor shadow(Colors::shadowColor(styleOptions)); + + // render + _helper->renderSliderHandle(painter, handleRect, background, outline, shadow, sunken, enabled && windowActive, tickSide); + } + + return true; +} + +//______________________________________________________________ +bool Style::drawDialComplexControl(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionSlider *sliderOption(qstyleoption_cast(option)); + if (!sliderOption) { + return true; + } + + const QPalette &palette(option->palette); + const State &state(option->state); + bool enabled(state & State_Enabled); + bool mouseOver((state & State_Active) && enabled && (state & State_MouseOver)); + bool hasFocus(enabled && (state & State_HasFocus)); + bool horizontal(sliderOption->orientation == Qt::Horizontal); + Side tickSide { SideNone }; + if (horizontal && sliderOption->tickPosition == QSlider::TicksAbove) { + tickSide = (Side)((int) tickSide | (int) SideTop); + } + if (horizontal && sliderOption->tickPosition == QSlider::TicksBelow) { + tickSide = (Side)((int) tickSide | (int) SideBottom); + } + if (!horizontal && sliderOption->tickPosition == QSlider::TicksLeft) { + tickSide = (Side)((int) tickSide | (int) SideLeft); + } + if (!horizontal && sliderOption->tickPosition == QSlider::TicksRight) { + tickSide = (Side)((int) tickSide | (int) SideRight); + } + + // do not render tickmarks + if (sliderOption->subControls & SC_DialTickmarks) + {} + + // groove + if (sliderOption->subControls & SC_DialGroove) { + // groove rect + QRect grooveRect(subControlRect(CC_Dial, sliderOption, SC_SliderGroove, widget)); + + // groove + QColor grooveColor(Colors::mix(palette.color(QPalette::Window), palette.color(QPalette::WindowText), 0.3)); + + // render groove + _helper->renderDialGroove(painter, grooveRect, grooveColor); + + if (enabled) { + // highlight + QColor highlight(palette.color(QPalette::Highlight)); + + // angles + qreal first(dialAngle(sliderOption, sliderOption->minimum)); + qreal second(dialAngle(sliderOption, sliderOption->sliderPosition)); + + // render contents + _helper->renderDialContents(painter, grooveRect, highlight, first, second); + } + } + + // handle + if (sliderOption->subControls & SC_DialHandle) { + // get handle rect + QRect handleRect(subControlRect(CC_Dial, sliderOption, SC_DialHandle, widget)); + handleRect = centerRect(handleRect, Metrics::Slider_ControlThickness, Metrics::Slider_ControlThickness); + + // handle state + bool handleActive(mouseOver && handleRect.contains(_animations->dialEngine().position(widget))); + bool sunken(state & (State_On | State_Sunken)); + + // animation state + _animations->dialEngine().setHandleRect(widget, handleRect); + _animations->dialEngine().updateState(widget, AnimationHover, handleActive && mouseOver); + _animations->dialEngine().updateState(widget, AnimationFocus, hasFocus); + AnimationMode mode(_animations->dialEngine().buttonAnimationMode(widget)); + qreal opacity(_animations->dialEngine().buttonOpacity(widget)); + + // Style options + StyleOptions styleOptions(palette); + styleOptions.setAnimationMode(mode); + styleOptions.setMouseOver(handleActive && mouseOver); + styleOptions.setHasFocus(hasFocus); + styleOptions.setOpacity(opacity); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + // define colors + QColor background(palette.color(QPalette::Button)); + QColor outline(Colors::sliderOutlineColor(styleOptions)); + QColor shadow(Colors::shadowColor(styleOptions)); + + // render + qreal angle = 270 - 180 * dialAngle(sliderOption, sliderOption->sliderPosition) / M_PI; + _helper->renderSliderHandle(painter, handleRect, background, outline, shadow, sunken, enabled, tickSide, angle); + } + + return true; +} + +//______________________________________________________________ +bool Style::drawScrollBarComplexControl(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const +{ + //the animation for QStyle::SC_ScrollBarGroove is special: it will animate + //the opacity of everything else as well, included slider and arrows + bool enabled(option->state & State_Enabled); + qreal opacity(_animations->scrollBarEngine().opacity(widget, QStyle::SC_ScrollBarGroove)); + bool animated(Adwaita::Config::ScrollBarShowOnMouseOver && _animations->scrollBarEngine().isAnimated(widget, AnimationHover, QStyle::SC_ScrollBarGroove)); + bool mouseOver((option->state & State_Active) && option->state & State_MouseOver); + + if (opacity == AnimationData::OpacityInvalid) { + opacity = 1; + } + + // render full groove directly, rather than using the addPage and subPage control element methods + if ((mouseOver || animated) && option->subControls & SC_ScrollBarGroove) { + // retrieve groove rectangle + QRect grooveRect(subControlRect(CC_ScrollBar, option, SC_ScrollBarGroove, widget)); + + const QPalette &palette(option->palette); + QColor color; + if (_dark) { + color = Colors::mix(palette.color(QPalette::Window), Colors::mix(palette.color(QPalette::Base), palette.color(QPalette::Window), 0.5), opacity); + } else { + color = Colors::mix(palette.color(QPalette::Window), Colors::mix(palette.color(QPalette::Window), palette.color(QPalette::Text), 0.2), opacity); + } + + const State &state(option->state); + bool horizontal(state & State_Horizontal); + + if (horizontal) { + grooveRect = centerRect(grooveRect, grooveRect.width(), Metrics::ScrollBar_SliderWidth); + } else { + grooveRect = centerRect(grooveRect, Metrics::ScrollBar_SliderWidth, grooveRect.height()); + } + + // render + if (enabled) { + painter->setPen(Qt::NoPen); + painter->setBrush(color); + painter->drawRect(option->rect); + } + } + + // call base class primitive + //ParentStyleClass::drawComplexControl( CC_ScrollBar, option, painter, widget ); + if (const QStyleOptionSlider *scrollbar = qstyleoption_cast(option)) { + QStyleOptionSlider newScrollbar = *scrollbar; + State saveFlags = scrollbar->state; + + if (scrollbar->subControls & SC_ScrollBarSlider) { + newScrollbar.rect = scrollbar->rect; + newScrollbar.state = saveFlags; + newScrollbar.rect = proxy()->subControlRect(CC_ScrollBar, &newScrollbar, SC_ScrollBarSlider, widget); + if (newScrollbar.rect.isValid()) { + proxy()->drawControl(CE_ScrollBarSlider, &newScrollbar, painter, widget); + + if (scrollbar->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(newScrollbar); + fropt.rect.setRect(newScrollbar.rect.x() + 2, newScrollbar.rect.y() + 2, + newScrollbar.rect.width() - 5, + newScrollbar.rect.height() - 5); + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget); + } + } + } + } + + return true; +} + +//______________________________________________________________ +bool Style::drawTitleBarComplexControl(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const +{ + // cast option and check + const QStyleOptionTitleBar *titleBarOption(qstyleoption_cast(option)); + if (!titleBarOption) { + return true; + } + + // store palette and rect + QPalette palette(option->palette); + const QRect &rect(option->rect); + + const State &flags(option->state); + bool enabled(flags & State_Enabled); + bool active(enabled && (titleBarOption->titleBarState & Qt::WindowActive)); + + // Style options + StyleOptions styleOptions(palette); + styleOptions.setActive(active); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + if (titleBarOption->subControls & SC_TitleBarLabel) { + // render background + painter->setClipRect(rect); + QColor outline(Colors::frameOutlineColor(styleOptions)); + QColor background(Colors::titleBarColor(styleOptions)); + _helper->renderTabWidgetFrame(painter, rect.adjusted(-1, -1, 1, 3), background, outline, CornersTop); + + painter->setRenderHint(QPainter::Antialiasing, false); + painter->setBrush(Qt::NoBrush); + painter->setPen(outline); + painter->drawLine(rect.bottomLeft(), rect.bottomRight()); + + // render text + palette.setColor(QPalette::WindowText, Colors::titleBarTextColor(styleOptions)); + QRect textRect(subControlRect(CC_TitleBar, option, SC_TitleBarLabel, widget)); + ParentStyleClass::drawItemText(painter, textRect, Qt::AlignCenter, palette, active, titleBarOption->text, QPalette::WindowText); + } + + // buttons + static const QList subControls = { + SC_TitleBarMinButton, + SC_TitleBarMaxButton, + SC_TitleBarCloseButton, + SC_TitleBarNormalButton, + SC_TitleBarSysMenu + }; + + // loop over supported buttons + foreach (const SubControl &subControl, subControls) { + // skip if not requested + if (!(titleBarOption->subControls & subControl)) { + continue; + } + + // find matching icon + QIcon icon; + switch (subControl) { + case SC_TitleBarMinButton: + icon = standardIcon(SP_TitleBarMinButton, option, widget); + break; + case SC_TitleBarMaxButton: + icon = standardIcon(SP_TitleBarMaxButton, option, widget); + break; + case SC_TitleBarCloseButton: + icon = standardIcon(SP_TitleBarCloseButton, option, widget); + break; + case SC_TitleBarNormalButton: + icon = standardIcon(SP_TitleBarNormalButton, option, widget); + break; + case SC_TitleBarSysMenu: + icon = titleBarOption->icon; + break; + default: + break; + } + + // check icon + if (icon.isNull()) { + continue; + } + + // define icon rect + QRect iconRect(subControlRect(CC_TitleBar, option, subControl, widget)); + if (iconRect.isEmpty()) { + continue; + } + + // active state + bool subControlActive(titleBarOption->activeSubControls & subControl); + + // mouse over state + const bool mouseOver(!subControlActive && widget && iconRect.translated(widget->mapToGlobal(QPoint(0, 0))).contains(QCursor::pos())); + + // adjust iconRect + int iconWidth(pixelMetric(PM_SmallIconSize, option, widget)); + QSize iconSize(iconWidth, iconWidth); + iconRect = centerRect(iconRect, iconSize); + + // set icon mode and state + QIcon::Mode iconMode; + QIcon::State iconState; + + if (!enabled) { + iconMode = QIcon::Disabled; + iconState = QIcon::Off; + } else { + if (mouseOver) { + iconMode = QIcon::Active; + } else { + iconMode = QIcon::Normal; + } + + iconState = subControlActive ? QIcon::On : QIcon::Off; + } + + // get pixmap and render + QPixmap pixmap = icon.pixmap(iconSize, iconMode, iconState); + painter->drawPixmap(iconRect, pixmap); + } + + return true; +} + +//____________________________________________________________________________________________________ +void Style::renderSpinBoxArrow(const SubControl &subControl, const QStyleOptionSpinBox *option, QPainter *painter, const QWidget *widget) const +{ + const QPalette &palette(option->palette); + const State &state(option->state); + + // enable state + bool hasFocus(state & State_HasFocus); + bool enabled(state & State_Enabled); + bool sunken(state & State_Sunken && option->activeSubControls & subControl); + const QColor &outline = Colors::frameOutlineColor(StyleOptions(palette)).lighter(120); + + // check steps enable step + const bool atLimit((subControl == SC_SpinBoxUp && !(option->stepEnabled & QAbstractSpinBox::StepUpEnabled)) + || (subControl == SC_SpinBoxDown && !(option->stepEnabled & QAbstractSpinBox::StepDownEnabled))); + + // update enabled state accordingly + enabled &= !atLimit; + + // update mouse-over effect + bool mouseOver((state & State_Active) && enabled && (state & State_MouseOver)); + + // check animation state + bool subControlHover(enabled && (mouseOver) && (option->activeSubControls & subControl)); + bool subControlSunken(enabled && (sunken) && (option->activeSubControls & subControl)); + _animations->spinBoxEngine().updateState(widget, subControl, subControlHover, subControlSunken); + + bool animated(enabled && _animations->spinBoxEngine().isAnimated(widget, subControl)); + qreal opacity(_animations->spinBoxEngine().opacity(widget, subControl)); + qreal pressedOpacity(_animations->spinBoxEngine().pressed(widget, subControl)); + + // Style options + StyleOptions styleOptions(palette); + styleOptions.setColorRole(QPalette::Text); + + QColor color = Colors::arrowOutlineColor(styleOptions); + if (atLimit) { + styleOptions.setColorGroup(QPalette::Disabled); + color = Colors::arrowOutlineColor(styleOptions); + } + + // arrow orientation + ArrowOrientation orientation((subControl == SC_SpinBoxUp) ? ArrowUp : ArrowDown); + + // arrow rect + QRect arrowRect(subControlRect(CC_SpinBox, option, subControl, widget)); + + if (subControl == SC_SpinBoxDown) { + painter->setBrush(Qt::NoBrush); + painter->setPen(outline); + int highlight = hasFocus ? 1 : 0; + painter->drawLine(arrowRect.left(), arrowRect.top() + 2 + highlight, arrowRect.left(), arrowRect.bottom() - 1 - highlight); + } + if (subControl == SC_SpinBoxUp) { + painter->setBrush(Qt::NoBrush); + painter->setPen(outline); + int highlight = hasFocus ? 1 : 0; + painter->drawLine(arrowRect.left(), arrowRect.top() + 2 + highlight, arrowRect.left(), arrowRect.bottom() - 1 - highlight); + } + + if (true) { + painter->setPen(Qt::NoPen); + QColor background = Colors::mix(palette.base().color(), palette.text().color(), opacity * 0.1); + background = Colors::mix(background, palette.dark().color(), pressedOpacity); + painter->setBrush(background); + if (hasFocus) { + painter->drawRect(arrowRect.adjusted(1, 3, -1, -2)); + } else { + painter->drawRect(arrowRect.adjusted(1, 2, -1, -1)); + } + } + + // render + _helper->renderSign(painter, arrowRect, color, orientation == ArrowUp); + + return; +} + +//______________________________________________________________________________ +void Style::renderMenuTitle(const QStyleOptionToolButton *option, QPainter *painter, const QWidget *) const +{ + // render a separator at the bottom + const QPalette &palette(option->palette); + + // Style options + StyleOptions styleOptions(palette); + styleOptions.setColorVariant(_dark ? Adwaita::ColorVariant::AdwaitaDark : Adwaita::ColorVariant::Adwaita); + + QColor color(Colors::separatorColor(styleOptions)); + _helper->renderSeparator(painter, QRect(option->rect.bottomLeft() - QPoint(0, Metrics::MenuItem_MarginWidth), QSize(option->rect.width(), 1)), color); + + // render text in the center of the rect + // icon is discarded on purpose + painter->setFont(option->font); + QRect contentsRect = insideMargin(option->rect, Metrics::MenuItem_MarginWidth); + drawItemText(painter, contentsRect, Qt::AlignCenter, palette, true, option->text, QPalette::WindowText); +} + +//______________________________________________________________________________ +qreal Style::dialAngle(const QStyleOptionSlider *sliderOption, int value) const +{ + // calculate angle at which handle needs to be drawn + qreal angle(0); + if (sliderOption->maximum == sliderOption->minimum) { + angle = M_PI / 2; + } else { + qreal fraction(qreal(value - sliderOption->minimum) / qreal(sliderOption->maximum - sliderOption->minimum)); + if (!sliderOption->upsideDown) { + fraction = 1 - fraction; + } + if (sliderOption->dialWrapping) { + angle = 1.5 * M_PI - fraction * 2 * M_PI; + } else { + angle = (M_PI * 8 - fraction * 10 * M_PI) / 6; + } + } + + return angle; +} + +//______________________________________________________________________________ +const QWidget *Style::scrollBarParent(const QWidget *widget) const +{ + // check widget and parent + if (!(widget && widget->parentWidget())) { + return nullptr; + } + + // try cast to scroll area. Must test both parent and grandparent + QAbstractScrollArea *scrollArea; + if (!(scrollArea = qobject_cast(widget->parentWidget()))) { + scrollArea = qobject_cast(widget->parentWidget()->parentWidget()); + } + + // check scrollarea + if (scrollArea && (widget == scrollArea->verticalScrollBar() || widget == scrollArea->horizontalScrollBar())) { + return scrollArea; + } else if (widget->parentWidget()->inherits("KTextEditor::View")) { + return widget->parentWidget(); + } else { + return nullptr; + } +} + +//______________________________________________________________________________ +QColor Style::scrollBarArrowColor(const QStyleOptionSlider *option, const SubControl &control, const QWidget *widget) const +{ + const QRect &rect(option->rect); + const QPalette &palette(option->palette); + + // Style options + StyleOptions styleOptions(palette); + styleOptions.setColorRole(QPalette::WindowText); + + QColor color(Colors::arrowOutlineColor(styleOptions)); + + bool widgetMouseOver((option->state & State_MouseOver) && (option->state & State_MouseOver)); + if (widget) { + widgetMouseOver = widget->underMouse(); + } else if (option->styleObject) { // in case this QStyle is used by QQuickControls QStyle wrapper + widgetMouseOver = option->styleObject->property("hover").toBool(); + } + + // check enabled state + bool enabled(option->state & State_Enabled); + if (!enabled) { + if (Adwaita::Config::ScrollBarShowOnMouseOver) { + // finally, global opacity when ScrollBarShowOnMouseOver + qreal globalOpacity(_animations->scrollBarEngine().opacity(widget, QStyle::SC_ScrollBarGroove)); + if (globalOpacity >= 0) { + color.setAlphaF(globalOpacity); + } else if (!widgetMouseOver) { // no mouse over and no animation in progress, don't draw arrows at all + return Qt::transparent; + } + } + return color; + } + + if ((control == SC_ScrollBarSubLine && option->sliderValue == option->minimum) + || (control == SC_ScrollBarAddLine && option->sliderValue == option->maximum)) { + + // Style options + styleOptions.setColorGroup(QPalette::Disabled); + + // manually disable arrow, to indicate that scrollbar is at limit + color = Colors::arrowOutlineColor(styleOptions); + if (Adwaita::Config::ScrollBarShowOnMouseOver) { + // finally, global opacity when ScrollBarShowOnMouseOver + qreal globalOpacity(_animations->scrollBarEngine().opacity(widget, QStyle::SC_ScrollBarGroove)); + if (globalOpacity >= 0) { + color.setAlphaF(globalOpacity); + } else if (!widgetMouseOver) { // no mouse over and no animation in progress, don't draw arrows at all + return Qt::transparent; + } + } + return color; + } + + bool mouseOver((option->state & State_Active) && _animations->scrollBarEngine().isHovered(widget, control)); + bool animated(_animations->scrollBarEngine().isAnimated(widget, AnimationHover, control)); + qreal opacity(_animations->scrollBarEngine().opacity(widget, control)); + + // retrieve mouse position from engine + QPoint position(mouseOver ? _animations->scrollBarEngine().position(widget) : QPoint(-1, -1)); + if (mouseOver && rect.contains(position)) { + /* + * need to update the arrow controlRect on fly because there is no + * way to get it from the styles directly, outside of repaint events + */ + _animations->scrollBarEngine().setSubControlRect(widget, control, rect); + } + + if (rect.intersects(_animations->scrollBarEngine().subControlRect(widget, control))) { + QColor highlight = Colors::hoverColor(StyleOptions(palette)); + if (animated) { + color = Colors::mix(color, highlight, opacity); + } else if (mouseOver) { + color = highlight; + } + } + + if (Adwaita::Config::ScrollBarShowOnMouseOver) { + // finally, global opacity when ScrollBarShowOnMouseOver + qreal globalOpacity(_animations->scrollBarEngine().opacity(widget, QStyle::SC_ScrollBarGroove)); + if (globalOpacity >= 0) { + color.setAlphaF(globalOpacity); + } else if (!widgetMouseOver) { // no mouse over and no animation in progress, don't draw arrows at all + return Qt::transparent; + } + } + + return color; +} + +//____________________________________________________________________________________ +void Style::setTranslucentBackground(QWidget *widget) const +{ + if (!_isKDE) { + return; + } + widget->setAttribute(Qt::WA_TranslucentBackground); + +#ifdef Q_WS_WIN + // FramelessWindowHint is needed on windows to make WA_TranslucentBackground work properly + widget->setWindowFlags(widget->windowFlags() | Qt::FramelessWindowHint); +#endif + +} + +//____________________________________________________________________________________ +QStyleOptionToolButton Style::separatorMenuItemOption(const QStyleOptionMenuItem *menuItemOption, const QWidget *widget) const +{ + // separator can have a title and an icon + // in that case they are rendered as sunken flat toolbuttons + QStyleOptionToolButton toolButtonOption; + toolButtonOption.initFrom(widget); + toolButtonOption.rect = menuItemOption->rect; + toolButtonOption.features = QStyleOptionToolButton::None; + toolButtonOption.state = State_Enabled | State_AutoRaise; + toolButtonOption.subControls = SC_ToolButton; + toolButtonOption.icon = QIcon(); + toolButtonOption.iconSize = QSize(); + toolButtonOption.text = menuItemOption->text; + + toolButtonOption.toolButtonStyle = Qt::ToolButtonTextBesideIcon; + return toolButtonOption; +} + +//____________________________________________________________________________________ +QIcon Style::toolBarExtensionIcon(StandardPixmap standardPixmap, const QStyleOption *option, const QWidget *widget) const +{ + // store palette + // due to Qt, it is not always safe to assume that either option, nor widget are defined + QPalette palette; + if (option) { + palette = option->palette; + } else if (widget) { + palette = widget->palette(); + } else { + palette = QApplication::palette(); + } + + // convenience class to map color to icon mode + struct IconData { + QColor _color; + QIcon::Mode _mode; + QIcon::State _state; + }; + + // map colors to icon states + const QList iconTypes = { + { palette.color(QPalette::Active, QPalette::WindowText), QIcon::Normal, QIcon::Off }, + { palette.color(QPalette::Active, QPalette::WindowText), QIcon::Selected, QIcon::Off }, + { palette.color(QPalette::Active, QPalette::WindowText), QIcon::Active, QIcon::Off }, + { palette.color(QPalette::Disabled, QPalette::WindowText), QIcon::Disabled, QIcon::Off }, + + { palette.color(QPalette::Active, QPalette::HighlightedText), QIcon::Normal, QIcon::On }, + { palette.color(QPalette::Active, QPalette::HighlightedText), QIcon::Selected, QIcon::On }, + { palette.color(QPalette::Active, QPalette::WindowText), QIcon::Active, QIcon::On }, + { palette.color(QPalette::Disabled, QPalette::WindowText), QIcon::Disabled, QIcon::On } + }; + + // default icon sizes + static const QList iconSizes = { 8, 16, 22, 32, 48 }; + + // decide arrow orientation + ArrowOrientation orientation(standardPixmap == SP_ToolBarHorizontalExtensionButton ? ArrowRight : ArrowDown); + + // create icon and fill + QIcon icon; + foreach (const IconData &iconData, iconTypes) { + foreach (const int &iconSize, iconSizes) { + // create pixmap + QPixmap pixmap(iconSize, iconSize); + pixmap.fill(Qt::transparent); + + // render + QPainter painter(&pixmap); + + // icon size + int fixedIconSize(pixelMetric(QStyle::PM_SmallIconSize, option, widget)); + QRect fixedRect(0, 0, fixedIconSize, fixedIconSize); + + painter.setWindow(fixedRect); + painter.translate(standardPixmap == SP_ToolBarHorizontalExtensionButton ? QPoint(1, 0) : QPoint(0, 1)); + _helper->renderArrow(&painter, fixedRect, iconData._color, orientation); + painter.end(); + + // add to icon + icon.addPixmap(pixmap, iconData._mode, iconData._state); + } + } + + return icon; +} + +//____________________________________________________________________________________ +QIcon Style::titleBarButtonIcon(StandardPixmap standardPixmap, const QStyleOption *option, const QWidget *widget) const +{ + // map standardPixmap to button type + ButtonType buttonType; + switch (standardPixmap) { + case SP_TitleBarNormalButton: + buttonType = ButtonRestore; + break; + case SP_TitleBarMinButton: + buttonType = ButtonMinimize; + break; + case SP_TitleBarMaxButton: + buttonType = ButtonMaximize; + break; + case SP_TitleBarCloseButton: + case SP_DockWidgetCloseButton: + buttonType = ButtonClose; + break; + + default: + return QIcon(); + } + + // store palette + // due to Qt, it is not always safe to assume that either option, nor widget are defined + QPalette palette; + if (option) { + palette = option->palette; + } else if (widget) { + palette = widget->palette(); + } else { + palette = QApplication::palette(); + } + + bool isCloseButton(buttonType == ButtonClose && Adwaita::Config::OutlineCloseButton); + + palette.setCurrentColorGroup(QPalette::Active); + QColor base(palette.color(QPalette::WindowText)); + QColor selected(palette.color(QPalette::HighlightedText)); + QColor negative(buttonType == ButtonClose ? Colors::negativeText(StyleOptions(palette)) : base); + QColor negativeSelected(buttonType == ButtonClose ? Colors::negativeText(StyleOptions(palette)) : selected); + + bool invertNormalState(isCloseButton); + + // convenience class to map color to icon mode + struct IconData { + QColor _color; + bool _inverted; + QIcon::Mode _mode; + QIcon::State _state; + }; + + // map colors to icon states + const QList iconTypes = { + // state off icons + { Colors::mix(palette.color(QPalette::Window), base, 0.5), invertNormalState, QIcon::Normal, QIcon::Off }, + { Colors::mix(palette.color(QPalette::Window), selected, 0.5), invertNormalState, QIcon::Selected, QIcon::Off }, + { Colors::mix(palette.color(QPalette::Window), negative, 0.5), true, QIcon::Active, QIcon::Off }, + { Colors::mix(palette.color(QPalette::Window), base, 0.2), invertNormalState, QIcon::Disabled, QIcon::Off }, + + // state on icons + { Colors::mix(palette.color(QPalette::Window), negative, 0.7), true, QIcon::Normal, QIcon::On }, + { Colors::mix(palette.color(QPalette::Window), negativeSelected, 0.7), true, QIcon::Selected, QIcon::On }, + { Colors::mix(palette.color(QPalette::Window), negative, 0.7), true, QIcon::Active, QIcon::On }, + { Colors::mix(palette.color(QPalette::Window), base, 0.2), invertNormalState, QIcon::Disabled, QIcon::On } + }; + + // default icon sizes + static const QList iconSizes = { 8, 16, 22, 32, 48 }; + + // output icon + QIcon icon; + + foreach (const IconData &iconData, iconTypes) { + foreach (const int &iconSize, iconSizes) { + // create pixmap + QPixmap pixmap(iconSize, iconSize); + pixmap.fill(Qt::transparent); + + // create painter and render + QPainter painter(&pixmap); + _helper->renderDecorationButton(&painter, pixmap.rect(), iconData._color, buttonType, iconData._inverted); + + painter.end(); + + // store + icon.addPixmap(pixmap, iconData._mode, iconData._state); + } + } + + return icon; +} + +//______________________________________________________________________________ +const QAbstractItemView *Style::itemViewParent(const QWidget *widget) const +{ + const QAbstractItemView *itemView(nullptr); + + // check widget directly + if ((itemView = qobject_cast(widget))) { + return itemView; + // check widget grand-parent + } else if (widget && widget->parentWidget() && (itemView = qobject_cast(widget->parentWidget()->parentWidget())) + && itemView->viewport() == widget->parentWidget()) { + return itemView; + } else { + return nullptr; + } +} + +//____________________________________________________________________ +bool Style::isSelectedItem(const QWidget *widget, const QPoint &localPosition) const +{ + // get relevant itemview parent and check + const QAbstractItemView *itemView(itemViewParent(widget)); + if (!(itemView && itemView->hasFocus() && itemView->selectionModel())) { + return false; + } + + QPoint position = widget->mapTo(itemView, localPosition); + + // get matching QModelIndex and check + QModelIndex index(itemView->indexAt(position)); + if (!index.isValid()) { + return false; + } + + // check whether index is selected + return itemView->selectionModel()->isSelected(index); +} + +//____________________________________________________________________ +bool Style::isQtQuickControl(const QStyleOption *option, const QWidget *widget) const +{ + return (widget == nullptr) && option && option->styleObject && option->styleObject->inherits("QQuickItem"); +} + +//____________________________________________________________________ +bool Style::showIconsInMenuItems(void) const +{ + return Adwaita::Settings::ShowIconsInMenuItems && !QCoreApplication::testAttribute(Qt::AA_DontShowIconsInMenus); +} + +//____________________________________________________________________ +bool Style::showIconsOnPushButtons(void) const +{ + return Adwaita::Settings::ShowIconsOnPushButtons; +} + +//____________________________________________________________________ +bool Style::isMenuTitle(const QWidget *widget) const +{ + // check widget + if (!widget) { + return false; + } + + // check property + QVariant property(widget->property(PropertyNames::menuTitle)); + if (property.isValid()) { + return property.toBool(); + } + + // detect menu toolbuttons + QWidget *parent = widget->parentWidget(); + if (qobject_cast(parent)) { + foreach (auto child, parent->findChildren()) { + if (child->defaultWidget() != widget) { + continue; + } + const_cast(widget)->setProperty(PropertyNames::menuTitle, true); + return true; + } + } + + const_cast(widget)->setProperty(PropertyNames::menuTitle, false); + return false; +} + +//____________________________________________________________________ +bool Style::hasAlteredBackground(const QWidget *widget) const +{ + // check widget + if (!widget) { + return false; + } + + // check property + QVariant property(widget->property(PropertyNames::alteredBackground)); + if (property.isValid()) { + return property.toBool(); + } + + // check if widget is of relevant type + bool hasAlteredBackground(false); + if (const QGroupBox *groupBox = qobject_cast(widget)) { + hasAlteredBackground = !groupBox->isFlat(); + } else if (const QTabWidget *tabWidget = qobject_cast(widget)) { + hasAlteredBackground = !tabWidget->documentMode(); + } else if (qobject_cast(widget)) { + hasAlteredBackground = true; + } else if (Adwaita::Config::DockWidgetDrawFrame && qobject_cast(widget)) { + hasAlteredBackground = true; + } + + if (widget->parentWidget() && !hasAlteredBackground) { + hasAlteredBackground = this->hasAlteredBackground(widget->parentWidget()); + } + const_cast(widget)->setProperty(PropertyNames::alteredBackground, hasAlteredBackground); + return hasAlteredBackground; +} + +} diff --git a/src/style/adwaitastyle.h b/src/style/adwaitastyle.h new file mode 100644 index 0000000..da9ae68 --- /dev/null +++ b/src/style/adwaitastyle.h @@ -0,0 +1,619 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * Copyright (C) 2014-2018 Martin Bříza * + * Copyright (C) 2019 Jan Grulich * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#ifndef ADWAITA_STYLE_H +#define ADWAITA_STYLE_H + +#include "adwaita.h" +#include "config-adwaita.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace AdwaitaPrivate +{ +class TabBarData; +} + +namespace Adwaita +{ +class Animations; +class Helper; +class Mnemonics; +class SplitterFactory; +class WidgetExplorer; +class WindowManager; + +//* convenience typedef for base class +using ParentStyleClass = QCommonStyle; + +//* base class for adwaita style +/** it is responsible to draw all the primitives to be displayed on screen, on request from Qt paint engine */ +class Style : public ParentStyleClass +{ + Q_OBJECT + + /* this tells kde applications that custom style elements are supported, using the kstyle mechanism */ + Q_CLASSINFO("X-KDE-CustomElements", "true") + +public: + //* constructor + explicit Style(bool dark); + + //* destructor + virtual ~Style(void); + + //* needed to avoid warnings at compilation time + using ParentStyleClass::polish; + using ParentStyleClass::unpolish; + + //* widget polishing + virtual void polish(QWidget *widget); + + //* widget unpolishing + virtual void unpolish(QWidget *widget); + + //* palette polishing + virtual void polish(QPalette &palette); + + virtual QPalette standardPalette() const; + + //* polish scrollarea + void polishScrollArea(QAbstractScrollArea *scrollArea); + + //* pixel metrics + virtual int pixelMetric(PixelMetric metric, const QStyleOption *option = nullptr, const QWidget *widget = nullptr) const; + + //* style hints + virtual int styleHint(StyleHint hint, const QStyleOption *option = nullptr, const QWidget *widget = nullptr, QStyleHintReturn *returnData = nullptr) const; + + //* returns rect corresponding to one widget's subelement + virtual QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const; + + //* returns rect corresponding to one widget's subcontrol + virtual QRect subControlRect(ComplexControl element, const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const; + + //* returns size matching contents + QSize sizeFromContents(ContentsType element, const QStyleOption *option, const QSize &size, const QWidget *widget) const; + + //* returns which subcontrol given QPoint corresponds to + SubControl hitTestComplexControl(ComplexControl control, const QStyleOptionComplex *option , const QPoint &point, const QWidget *widget) const; + + //* primitives + void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + + //* controls + void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + + //* complex controls + void drawComplexControl(ComplexControl element, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const; + + //* generic text rendering + virtual void drawItemText(QPainter *painter, const QRect &rect, int alignment, const QPalette &palette, bool enabled, + const QString &text, QPalette::ColorRole textRole = QPalette::NoRole) const; + + //*@name event filters + //@{ + + virtual bool eventFilter(QObject *object, QEvent *event); + bool eventFilterScrollArea(QWidget *widget, QEvent *event); + bool eventFilterComboBoxContainer(QWidget *widget, QEvent *event); + bool eventFilterDockWidget(QDockWidget *dockWidget, QEvent *event); + bool eventFilterMdiSubWindow(QMdiSubWindow *subWindow, QEvent *event); + + bool eventFilterCommandLinkButton(QCommandLinkButton *button, QEvent *event); + + //* install event filter to object, in a unique way + void addEventFilter(QObject *object) + { + object->removeEventFilter(this); + object->installEventFilter(this); + } + + //@} + +protected Q_SLOTS: + + //* update configuration + void configurationChanged(void); + + //* standard icons + virtual QIcon standardIconImplementation(StandardPixmap standardPixmap, const QStyleOption *option, const QWidget *widget) const; + +protected: + + //* standard icons + virtual QIcon standardIcon(StandardPixmap pixmap, const QStyleOption *option = nullptr, const QWidget *widget = nullptr) const + { + return standardIconImplementation(pixmap, option, widget); + } + + //* load configuration + void loadConfiguration(); + + //*@name subelementRect specialized functions + //@{ + + //* default implementation. Does not change anything + QRect defaultSubElementRect(const QStyleOption *option, const QWidget *widget) const + { + return option->rect; + } + + QRect pushButtonContentsRect(const QStyleOption *option, const QWidget *widget) const; + QRect pushButtonFocusRect(const QStyleOption *option, const QWidget *widget) const; + QRect checkBoxContentsRect(const QStyleOption *option, const QWidget *widget) const; + QRect checkBoxIndicatorRect(const QStyleOption *option, const QWidget *widget) const; + QRect checkBoxFocusRect(const QStyleOption *option, const QWidget *widget) const; + QRect lineEditContentsRect(const QStyleOption *option, const QWidget *widget) const; + QRect progressBarGrooveRect(const QStyleOption *option, const QWidget *widget) const; + QRect progressBarContentsRect(const QStyleOption *option, const QWidget *widget) const; + QRect progressBarLabelRect(const QStyleOption *option, const QWidget *widget) const; + QRect headerArrowRect(const QStyleOption *option, const QWidget *widget) const; + QRect headerLabelRect(const QStyleOption *option, const QWidget *widget) const; + QRect sliderFocusRect(const QStyleOption *option, const QWidget *widget) const; + QRect tabBarTabLeftButtonRect(const QStyleOption *option, const QWidget *widget) const; + QRect tabBarTabRightButtonRect(const QStyleOption *option, const QWidget *widget) const; + QRect tabWidgetTabBarRect(const QStyleOption *option, const QWidget *widget) const; + QRect tabWidgetTabContentsRect(const QStyleOption *option, const QWidget *widget) const; + QRect tabWidgetTabPaneRect(const QStyleOption *option, const QWidget *widget) const; + QRect tabWidgetCornerRect(SubElement element, const QStyleOption *option, const QWidget *widget) const; + QRect toolBoxTabContentsRect(const QStyleOption *option, const QWidget *widget) const; + QRect genericLayoutItemRect(const QStyleOption *option, const QWidget *widget) const; + + //@}option + + //*@name subcontrol Rect specialized functions + //@{ + + QRect groupBoxSubControlRect(const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const; + QRect toolButtonSubControlRect(const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const; + QRect comboBoxSubControlRect(const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const; + QRect spinBoxSubControlRect(const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const; + QRect scrollBarInternalSubControlRect(const QStyleOptionComplex *option, SubControl subControl) const; + QRect scrollBarSubControlRect(const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const; + QRect dialSubControlRect(const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const; + QRect sliderSubControlRect(const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const; + + //@} + + //*@name sizeFromContents + //@{ + QSize defaultSizeFromContents(const QStyleOption *option, const QSize &size, const QWidget *widget) const + { + return size; + } + + QSize checkBoxSizeFromContents(const QStyleOption *option, const QSize &size, const QWidget *widget) const; + QSize lineEditSizeFromContents(const QStyleOption *option, const QSize &size, const QWidget *widget) const; + QSize comboBoxSizeFromContents(const QStyleOption *option, const QSize &size, const QWidget *widget) const; + QSize spinBoxSizeFromContents(const QStyleOption *option, const QSize &size, const QWidget *widget) const; + QSize sliderSizeFromContents(const QStyleOption *option, const QSize &size, const QWidget *widget) const; + QSize pushButtonSizeFromContents(const QStyleOption *option, const QSize &size, const QWidget *widget) const; + QSize toolButtonSizeFromContents(const QStyleOption *option, const QSize &size, const QWidget *widget) const; + QSize menuBarItemSizeFromContents(const QStyleOption *option, const QSize &size, const QWidget *widget) const; + QSize menuItemSizeFromContents(const QStyleOption *option, const QSize &size, const QWidget *widget) const; + QSize progressBarSizeFromContents(const QStyleOption *option, const QSize &size, const QWidget *widget) const; + QSize tabWidgetSizeFromContents(const QStyleOption *option, const QSize &size, const QWidget *widget) const; + QSize tabBarTabSizeFromContents(const QStyleOption *option, const QSize &size, const QWidget *widget) const; + QSize headerSectionSizeFromContents(const QStyleOption *option, const QSize &size, const QWidget *widget) const; + QSize itemViewItemSizeFromContents(const QStyleOption *option, const QSize &size, const QWidget *widget) const; + + //@} + + //*@name primitives specialized functions + //@{ + + bool emptyPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const + { + return true; + } + + bool drawFramePrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawFrameLineEditPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawFrameFocusRectPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawFrameMenuPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawFrameGroupBoxPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawFrameTabWidgetPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawFrameTabBarBasePrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawFrameWindowPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + + bool drawIndicatorArrowUpPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const + { + return drawIndicatorArrowPrimitive(ArrowUp, option, painter, widget); + } + + bool drawIndicatorArrowDownPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const + { + return drawIndicatorArrowPrimitive(ArrowDown, option, painter, widget); + } + + bool drawIndicatorArrowLeftPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const + { + return drawIndicatorArrowPrimitive(ArrowLeft, option, painter, widget); + } + + bool drawIndicatorArrowRightPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const + { + return drawIndicatorArrowPrimitive(ArrowRight, option, painter, widget); + } + + bool drawIndicatorArrowPrimitive(ArrowOrientation orientation, const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawIndicatorHeaderArrowPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawPanelButtonCommandPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawPanelButtonToolPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawTabBarPanelButtonToolPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawPanelScrollAreaCornerPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawPanelMenuPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawPanelTipLabelPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawPanelItemViewRowPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawPanelItemViewItemPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawIndicatorCheckBoxPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawIndicatorRadioButtonPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawIndicatorButtonDropDownPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawIndicatorTabClosePrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawIndicatorTabTearPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawIndicatorToolBarHandlePrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawIndicatorToolBarSeparatorPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + bool drawIndicatorBranchPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + + //@} + + //*@name controls specialized functions + //@{ + + bool emptyControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const + { + return true; + } + + virtual bool drawPushButtonLabelControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawToolButtonLabelControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawCheckBoxLabelControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawComboBoxLabelControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawItemViewItemControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawMenuBarEmptyArea(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawMenuBarItemControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawMenuItemControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawProgressBarControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawProgressBarContentsControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawProgressBarGrooveControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawProgressBarLabelControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawScrollBarSliderControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawScrollBarAddLineControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawScrollBarSubLineControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawShapedFrameControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawRubberBandControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawHeaderSectionControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawHeaderLabelControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawHeaderEmptyAreaControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawTabBarTabLabelControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawTabBarTabShapeControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawToolBoxTabLabelControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawToolBoxTabShapeControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + virtual bool drawDockWidgetTitleControl(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + + //*@} + + //*@name complex ontrols specialized functions + //@{ + bool drawGroupBoxComplexControl(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const; + bool drawToolButtonComplexControl(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const; + bool drawComboBoxComplexControl(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const; + bool drawSpinBoxComplexControl(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const; + bool drawSliderComplexControl(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const; + bool drawDialComplexControl(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const; + bool drawScrollBarComplexControl(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const; + bool drawTitleBarComplexControl(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const; + //@} + + //!*@name various utilty functions + //@{ + + //* spinbox arrows + void renderSpinBoxArrow(const SubControl &subControl, const QStyleOptionSpinBox *option, QPainter *painter, const QWidget *widget) const; + + //* menu title + void renderMenuTitle(const QStyleOptionToolButton *option, QPainter *painter, const QWidget *widget) const; + + //* return dial angle based on option and value + qreal dialAngle(const QStyleOptionSlider *sliderOption, int value) const; + + //* returns relevant scrollbar parent + /** needed to detect parent focus */ + const QWidget *scrollBarParent(const QWidget *widget) const; + + //* returns true if given scrollbar arrow is animated + QColor scrollBarArrowColor(const QStyleOptionSlider *option, const SubControl &control, const QWidget *widget) const; + + //* scrollbar buttons + enum ScrollBarButtonType { + NoButton, + SingleButton, + DoubleButton + }; + + //* returns height for scrollbar buttons depending of button types + int scrollBarButtonHeight(const ScrollBarButtonType &type) const + { + switch (type) { + case NoButton: + return Metrics::ScrollBar_NoButtonHeight; + case SingleButton: + return Metrics::ScrollBar_SingleButtonHeight; + case DoubleButton: + return Metrics::ScrollBar_DoubleButtonHeight; + default: + return 0; + } + } + + //@} + + //* translucent background + void setTranslucentBackground(QWidget *widget) const; + + /** + separator can have a title and an icon + in that case they are rendered as sunken flat toolbuttons + return toolbutton option that matches named separator menu items + */ + QStyleOptionToolButton separatorMenuItemOption(const QStyleOptionMenuItem *menuItemOption, const QWidget *widget) const; + + //* create toolbar extension icon + QIcon toolBarExtensionIcon(StandardPixmap, const QStyleOption *option, const QWidget *widget) const; + + //* create title bar button icon + QIcon titleBarButtonIcon(StandardPixmap, const QStyleOption *option, const QWidget *widget) const; + + //* returns item view parent if any + /** needed to have correct color on focused checkboxes and radiobuttons */ + const QAbstractItemView *itemViewParent(const QWidget *widget) const; + + //* returns true if a given widget is a selected item in a focused list + /** + This is necessary to have the correct colors used for e.g. checkboxes and radiobuttons in lists + @param widget The widget to be checked + @param position Used to find the relevant QModelIndex + */ + bool isSelectedItem(const QWidget *widget, const QPoint &localPosition) const; + + //* return true if option corresponds to QtQuick control + bool isQtQuickControl(const QStyleOption *option, const QWidget *widget) const; + + //@} + + //* adjust rect based on provided margins + QRect insideMargin(const QRect &r, int margin) const + { + return insideMargin(r, margin, margin); + } + + //* adjust rect based on provided margins + QRect insideMargin(const QRect &r, int marginWidth, int marginHeight) const + { + return r.adjusted(marginWidth, marginHeight, -marginWidth, -marginHeight); + } + + //* expand size based on margins + QSize expandSize(const QSize &size, int margin) const + { + return expandSize(size, margin, margin); + } + + //* expand size based on margins + QSize expandSize(const QSize &size, int marginWidth, int marginHeight) const + { + return size + 2 * QSize(marginWidth, marginHeight); + } + + //* returns true for vertical tabs + bool isVerticalTab(const QStyleOptionTab *option) const + { + return isVerticalTab(option->shape); + } + + bool isVerticalTab(const QTabBar::Shape &shape) const + { + return shape == QTabBar::RoundedEast + || shape == QTabBar::RoundedWest + || shape == QTabBar::TriangularEast + || shape == QTabBar::TriangularWest; + + } + + //* right to left alignment handling + using ParentStyleClass::visualRect; + QRect visualRect(const QStyleOption *opt, const QRect &subRect) const + { + return ParentStyleClass::visualRect(opt->direction, opt->rect, subRect); + } + + //* centering + QRect centerRect(const QRect &rect, const QSize &size) const + { + return centerRect(rect, size.width(), size.height()); + } + + QRect centerRect(const QRect &rect, int width, int height) const + { + return QRect(rect.left() + (rect.width() - width) / 2, rect.top() + (rect.height() - height) / 2, width, height); + } + + /* + Checks whether the point is before the bound rect for bound of given orientation. + This is needed to implement custom number of buttons in scrollbars, + as well as proper mouse-hover + */ + inline bool preceeds(const QPoint &point, const QRect &bound, const QStyleOption *option) const; + + //* return which arrow button is hit by point for scrollbar double buttons + inline QStyle::SubControl scrollBarHitTest(const QRect &rect, const QPoint &point, const QStyleOption *option) const; + + //! return true if one of the widget's parent inherits requested type + inline bool hasParent(const QWidget *widget, const char *className) const; + + //* return true if one of the widget's parent inherits requested type + template bool hasParent(const QWidget *widget) const; + + //* return true if icons should be shown in menus + bool showIconsInMenuItems(void) const; + + //* return true if icons should be shown on buttons + bool showIconsOnPushButtons(void) const; + + //* return true if passed widget is a menu title (KMenu::addTitle) + bool isMenuTitle(const QWidget *widget) const; + + //* return true if passed widget is a menu title (KMenu::addTitle) + bool hasAlteredBackground(const QWidget *widget) const; + +private: + + //*@name scrollbar button types (for addLine and subLine ) + //@{ + ScrollBarButtonType _addLineButtons; + ScrollBarButtonType _subLineButtons; + //@} + + //* helper + Helper *_helper; + + //* animations + Animations *_animations; + + //* keyboard accelerators + Mnemonics *_mnemonics; + + //* window manager + WindowManager *_windowManager; + + //* splitter Factory, to extend splitters hit area + SplitterFactory *_splitterFactory; + + //* widget explorer + WidgetExplorer *_widgetExplorer; + + //* tabbar data + AdwaitaPrivate::TabBarData *_tabBarData; + + //* icon hash + using IconCache = QHash; + IconCache _iconCache; + + //* pointer to primitive specialized function + using StylePrimitive = bool(Style::*)(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + StylePrimitive _frameFocusPrimitive = nullptr; + + //* pointer to control specialized function + using StyleControl = bool (Style::*)(const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + + //* pointer to control specialized function + using StyleComplexControl = bool (Style::*)(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const; + + //*@name custom elements + //@{ + + //* use Argb Drag and Drop Window + QStyle::StyleHint SH_ArgbDndWindow; + + //! styled painting for KCapacityBar + QStyle::ControlElement CE_CapacityBar; + + bool _dark { false }; + + bool _isGNOME { false }; + bool _isKDE { false }; + + //@} +}; + +//_________________________________________________________________________ +bool Style::preceeds(const QPoint &point, const QRect &bound, const QStyleOption *option) const +{ + if (option->state & QStyle::State_Horizontal) { + if (option->direction == Qt::LeftToRight) + return point.x() < bound.right(); + else + return point.x() > bound.x(); + + } else + return point.y() < bound.y(); +} + +//_________________________________________________________________________ +QStyle::SubControl Style::scrollBarHitTest(const QRect &rect, const QPoint &point, const QStyleOption *option) const +{ + if (option->state & QStyle::State_Horizontal) { + if (option->direction == Qt::LeftToRight) + return point.x() < rect.center().x() ? QStyle::SC_ScrollBarSubLine : QStyle::SC_ScrollBarAddLine; + else + return point.x() > rect.center().x() ? QStyle::SC_ScrollBarSubLine : QStyle::SC_ScrollBarAddLine; + + } else + return point.y() < rect.center().y() ? QStyle::SC_ScrollBarSubLine : QStyle::SC_ScrollBarAddLine; +} + +//_________________________________________________________________________ +bool Style::hasParent(const QWidget *widget, const char *className) const +{ + + if (!widget) + return false; + + while ((widget = widget->parentWidget())) { + if (widget->inherits(className)) + return true; + } + + return false; + +} + +//_________________________________________________________________________ +template< typename T > bool Style::hasParent(const QWidget *widget) const +{ + + if (!widget) + return false; + + while ((widget = widget->parentWidget())) { + if (qobject_cast(widget)) + return true; + } + + return false; +} + +} // namespace Adwaita + +#endif // ADWAITA_STYLE_H + diff --git a/src/style/adwaitastyleplugin.cpp b/src/style/adwaitastyleplugin.cpp new file mode 100644 index 0000000..6f5496d --- /dev/null +++ b/src/style/adwaitastyleplugin.cpp @@ -0,0 +1,51 @@ + +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include "adwaitastyleplugin.h" +#include "adwaitastyle.h" + +#include + +namespace Adwaita +{ + +QStyle *StylePlugin::create(const QString &key) +{ + if (key.toLower() == QStringLiteral("adwaita")) { + return new Style(false); + } + + if (key.toLower() == QStringLiteral("adwaita-dark")) { + return new Style(true); + } + + return nullptr; +} + +StylePlugin::~StylePlugin() +{ +} + +QStringList StylePlugin::keys() const +{ + return QStringList() << QStringLiteral("Adwaita") << QStringLiteral("Adwaita-Dark"); +} + +} // namespace Adwaita diff --git a/src/style/adwaitastyleplugin.h b/src/style/adwaitastyleplugin.h new file mode 100644 index 0000000..607b50c --- /dev/null +++ b/src/style/adwaitastyleplugin.h @@ -0,0 +1,52 @@ +#ifndef ADWAITA_STYLE_PLUGIN_H +#define ADWAITA_STYLE_PLUGIN_H + +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + *************************************************************************/ + +#include + +namespace Adwaita +{ + +class StylePlugin : public QStylePlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QStyleFactoryInterface" FILE "adwaita.json") +public: + + //* constructor + explicit StylePlugin(QObject *parent = 0): + QStylePlugin(parent) + {} + + //* destructor + ~StylePlugin(); + + //* returns list of valid keys + QStringList keys() const; + + //* create style + QStyle *create(const QString &key); +}; + +} // namespace Adwaita + +#endif // ADWAITA_STYLE_PLUGIN_H diff --git a/tests/test.cpp b/tests/test.cpp new file mode 100644 index 0000000..53b9def --- /dev/null +++ b/tests/test.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 Jan Grulich + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "test.h" + +#include + +#include +#include + +#include + +QTEST_MAIN(Test) + +void Test::initTestCase() +{ + // Just simple test whether we can link against the Adwaita library + // and find headers + std::unique_ptr options(new Adwaita::StyleOptions(QPalette())); + QVERIFY(options); +} diff --git a/tests/test.h b/tests/test.h new file mode 100644 index 0000000..04ccc55 --- /dev/null +++ b/tests/test.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 Jan Grulich + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef ADWAITA_TEST_H +#define ADWAITA_TEST_H + +#include +#include + +class Test : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); +}; + +#endif // ADWAITAQT_TEST_H