[
  {
    "path": ".clang-format",
    "content": "---\nLanguage:        Cpp\n# BasedOnStyle:  WebKit\nAccessModifierOffset: -4\nAlignAfterOpenBracket: DontAlign\nAlignConsecutiveAssignments: false\nAlignConsecutiveDeclarations: false\nAlignEscapedNewlines: Right\nAlignOperands:   false\nAlignTrailingComments: true\nAllowAllParametersOfDeclarationOnNextLine: true\nAllowShortBlocksOnASingleLine: false\nAllowShortCaseLabelsOnASingleLine: false\nAllowShortFunctionsOnASingleLine: All\nAllowShortIfStatementsOnASingleLine: false\nAllowShortLoopsOnASingleLine: false\nAlwaysBreakAfterDefinitionReturnType: None\nAlwaysBreakAfterReturnType: None\nAlwaysBreakBeforeMultilineStrings: false\nAlwaysBreakTemplateDeclarations: false\nBinPackArguments: true\nBinPackParameters: true\nBraceWrapping:\n  AfterClass:      false\n  AfterControlStatement: false\n  AfterEnum:       false\n  AfterFunction:   true\n  AfterNamespace:  false\n  AfterObjCDeclaration: false\n  AfterStruct:     false\n  AfterUnion:      false\n  AfterExternBlock: false\n  BeforeCatch:     false\n  BeforeElse:      false\n  IndentBraces:    false\n  SplitEmptyFunction: true\n  SplitEmptyRecord: true\n  SplitEmptyNamespace: true\nBreakBeforeBinaryOperators: All\nBreakBeforeBraces: WebKit\nBreakBeforeInheritanceComma: false\nBreakBeforeTernaryOperators: true\nBreakConstructorInitializersBeforeComma: false\nBreakConstructorInitializers: BeforeComma\nBreakAfterJavaFieldAnnotations: false\nBreakStringLiterals: true\nColumnLimit:     0\nCommentPragmas:  '^ IWYU pragma:'\nCompactNamespaces: false\nConstructorInitializerAllOnOneLineOrOnePerLine: false\nConstructorInitializerIndentWidth: 4\nContinuationIndentWidth: 4\nCpp11BracedListStyle: false\nDerivePointerAlignment: false\nDisableFormat:   false\nExperimentalAutoDetectBinPacking: false\nFixNamespaceComments: false\nForEachMacros:\n  - foreach\n  - Q_FOREACH\n  - BOOST_FOREACH\nIncludeBlocks:   Preserve\nIncludeCategories:\n  - Regex:           '^\"(llvm|llvm-c|clang|clang-c)/'\n    Priority:        2\n  - Regex:           '^(<|\"(gtest|gmock|isl|json)/)'\n    Priority:        3\n  - Regex:           '.*'\n    Priority:        1\nIncludeIsMainRegex: '(Test)?$'\nIndentCaseLabels: false\nIndentPPDirectives: None\nIndentWidth:     4\nIndentWrappedFunctionNames: false\nJavaScriptQuotes: Leave\nJavaScriptWrapImports: true\nKeepEmptyLinesAtTheStartOfBlocks: false\nMacroBlockBegin: ''\nMacroBlockEnd:   ''\nMaxEmptyLinesToKeep: 1\nNamespaceIndentation: Inner\nObjCBlockIndentWidth: 4\nObjCSpaceAfterProperty: true\nObjCSpaceBeforeProtocolList: true\nPenaltyBreakAssignment: 2\nPenaltyBreakBeforeFirstCallParameter: 19\nPenaltyBreakComment: 300\nPenaltyBreakFirstLessLess: 120\nPenaltyBreakString: 1000\nPenaltyExcessCharacter: 1000000\nPenaltyReturnTypeOnItsOwnLine: 60\nPointerAlignment: Left\nRawStringFormats:\n  - Delimiter:       pb\n    Language:        TextProto\n    BasedOnStyle:    google\nReflowComments:  true\nSortIncludes:    true\nSortUsingDeclarations: true\nSpaceAfterCStyleCast: false\nSpaceAfterTemplateKeyword: true\nSpaceBeforeAssignmentOperators: true\nSpaceBeforeParens: ControlStatements\nSpaceInEmptyParentheses: false\nSpacesBeforeTrailingComments: 1\nSpacesInAngles:  false\nSpacesInContainerLiterals: true\nSpacesInCStyleCastParentheses: false\nSpacesInParentheses: false\nSpacesInSquareBrackets: false\nStandard:        Cpp11\nTabWidth:        8\nUseTab:          Never\n...\n\n"
  },
  {
    "path": ".gitignore",
    "content": "/build\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"files.associations\": {\n        \"array\": \"cpp\",\n        \"string_view\": \"cpp\"\n    }\n}"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required (VERSION 3.16.0)\nproject (material-decoration)\n\nadd_definitions (-Wall -Werror)\n\ninclude (FeatureSummary)\nfind_package (ECM 0.0.9 REQUIRED NO_MODULE)\n\nset (CMAKE_MODULE_PATH\n    ${CMAKE_MODULE_PATH}\n    ${ECM_MODULE_PATH}\n)\n\ninclude (ECMInstallIcons)\ninclude (KDEInstallDirs)\ninclude (KDECMakeSettings)\ninclude (KDECompilerSettings NO_POLICY_SCOPE)\n\n# set(QT_MIN_VERSION \"5.9.0\")\n# https://doc.qt.io/qt-6/cmake-qt5-and-qt6-compatibility.html#supporting-older-qt-5-versions\n# find_package(QT NAMES Qt6 Qt5)\nfind_package(QT NAMES Qt5)\n# find_package(QT NAMES Qt6)\nfind_package(Qt${QT_VERSION_MAJOR} CONFIG REQUIRED COMPONENTS\n    Widgets\n    DBus\n)\ninclude(ECMQtDeclareLoggingCategory)\n\n# Remove Qt 5.15 Deprecations\n# https://doc.qt.io/qt-6/portingguide.html\nadd_compile_definitions(QT_DISABLE_DEPRECATED_BEFORE=0x050F00)\n\nadd_subdirectory (src/libdbusmenuqt)\nadd_subdirectory (src)\n\nfeature_summary(WHAT ALL)\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "README.md",
    "content": "![Demo](data/preview.png)\n\n## material-decoration\n\nMaterial-ish window decoration theme for KWin.\n\n### Locally Integrated Menus\n\nThis hides the AppMenu icon button and draws the menu in the titlebar.\n\n![](https://i.imgur.com/oFOVWjV.png)\n\nMake sure you add the AppMenu button in System Settings > Application Style > Window Decorations > Buttons Tab.\n\nTODO/Bugs ([Issue #1](https://github.com/Zren/material-decoration/issues/1)):\n\n* Open Submenu on Shortcut (eg: `Alt+F`)\n* Display mnemonics when holding `Alt`\n\nUpstream LIM discussion in the KDE Bug report: https://bugs.kde.org/show_bug.cgi?id=375951#c27\n\n### Installation\n\n#### Binary package\n\n- Arch/Manjaro (AUR):  \n  Install the `material-kwin-decoration-git` AUR package.  \n  https://aur.archlinux.org/packages/material-kwin-decoration-git/\n\n- openSUSE:  \n  https://build.opensuse.org/package/show/home:trmdi/material-decoration\n```\nsudo zypper ar obs://home:trmdi trmdi\nsudo zypper in -r trmdi material-decoration\n```\n\n#### Building from source\nBuild dependencies:\n\n- Ubuntu:\n```\nsudo apt build-dep breeze\nsudo apt build-dep kwin\n```\n\n\nDownload the source:\n\n```\ncd ~/Downloads\ngit clone https://github.com/Zren/material-decoration.git\ncd material-decoration\n```\n\nThen compile the decoration, and install it:\n\n```\nmkdir build\ncd build\ncmake -DCMAKE_INSTALL_PREFIX=/usr ..\nmake\nsudo make install\n```\n\nSelect Material in System Settings > Application Style > Window Decorations.\n\nTo test changes, restart `kwin_x11` with:\n\n```\nQT_LOGGING_RULES=\"*=false;kdecoration.material=true\" kstart5 -- kwin_x11 --replace\n```\n\n### Update\n\nOn 2020 June 18, the kdecoration id was changed from `zzag` to `zren`. You will need to re-select `Material` in System Settings > Application Style > Window Decoration. KWin will fallback to `Breeze` if you forget to do this.\n\n#### Building from source\n\nFirst navigate to the source directory, and `git pull` recent changes.\n\n```\ncd ~/Downloads/material-decoration\ngit pull origin master --ff-only\n```\n\nThen re-run the install instructions.\n"
  },
  {
    "path": "src/AppIconButton.h",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// own\n#include \"Button.h\"\n#include \"Decoration.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n\n// KF\n#include <KIconLoader>\n\n// Qt\n#include <QPainter>\n#include <QPalette>\n\nnamespace Material\n{\n\nclass AppIconButton\n{\n\npublic:\n    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {\n        QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::iconChanged,\n            button, [button] {\n                button->update();\n            }\n        );\n    }\n    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {\n        Q_UNUSED(iconRect)\n\n        const QRectF contentRect = button->contentArea();\n        int appIconSize = qMax(16, qRound(gridUnit * 16));\n        QRectF appIconRect = QRectF(0, 0, appIconSize, appIconSize);\n        appIconRect.moveCenter(contentRect.center().toPoint());\n\n        const auto *deco = qobject_cast<Decoration *>(button->decoration());\n        auto *decoratedClient = deco->client().toStrongRef().data();\n\n        const QPalette activePalette = KIconLoader::global()->customPalette();\n        QPalette palette = decoratedClient->palette();\n        palette.setColor(QPalette::WindowText, deco->titleBarForegroundColor());\n        KIconLoader::global()->setCustomPalette(palette);\n        decoratedClient->icon().paint(painter, appIconRect.toRect());\n        if (activePalette == QPalette()) {\n            KIconLoader::global()->resetPalette();\n        } else {\n            KIconLoader::global()->setCustomPalette(palette);\n        }\n    }\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/AppMenuButton.cc",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2016 Kai Uwe Broulik <kde@privat.broulik.de>\n * Copyright (C) 2014 by Hugo Pereira Da Costa <hugo.pereira@free.fr>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n// own\n#include \"AppMenuButton.h\"\n#include \"Material.h\"\n#include \"Button.h\"\n#include \"Decoration.h\"\n#include \"AppMenuButtonGroup.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n\n// KF\n#include <KColorUtils>\n\n// Qt\n#include <QDebug>\n\n\nnamespace Material\n{\n\nAppMenuButton::AppMenuButton(Decoration *decoration, const int buttonIndex, QObject *parent)\n    : Button(KDecoration2::DecorationButtonType::Custom, decoration, parent)\n    , m_buttonIndex(buttonIndex)\n{\n    setCheckable(true);\n\n    connect(this, &AppMenuButton::clicked,\n        this, &AppMenuButton::trigger);\n\n    const auto *buttonGroup = qobject_cast<AppMenuButtonGroup *>(parent);\n    if (buttonGroup) {\n        setOpacity(buttonGroup->opacity());\n    }\n}\n\nAppMenuButton::~AppMenuButton()\n{\n}\n\nint AppMenuButton::buttonIndex() const\n{\n    return m_buttonIndex;\n}\n\nQColor AppMenuButton::backgroundColor() const\n{\n    const auto *buttonGroup = qobject_cast<AppMenuButtonGroup *>(parent());\n    if (buttonGroup\n        && buttonGroup->isMenuOpen()\n        && buttonGroup->currentIndex() != m_buttonIndex\n    ) {\n        return Qt::transparent;\n    } else {\n        return Button::backgroundColor();\n    }\n}\n\nQColor AppMenuButton::foregroundColor() const\n{\n    const auto *buttonGroup = qobject_cast<AppMenuButtonGroup *>(parent());\n    if (buttonGroup\n        && buttonGroup->isMenuOpen()\n        && buttonGroup->currentIndex() != m_buttonIndex\n    ) {\n        const auto *deco = qobject_cast<Decoration *>(decoration());\n        if (!deco) {\n            return {};\n        }\n        return KColorUtils::mix(\n            deco->titleBarBackgroundColor(),\n            deco->titleBarForegroundColor(),\n            0.8);\n    } else {\n        return Button::foregroundColor();\n    }\n}\n\nvoid AppMenuButton::trigger() {\n    // qCDebug(category) << \"AppMenuButton::trigger\" << m_buttonIndex;\n\n    auto *buttonGroup = qobject_cast<AppMenuButtonGroup *>(parent());\n    buttonGroup->trigger(m_buttonIndex);\n}\n\n} // namespace Material\n"
  },
  {
    "path": "src/AppMenuButton.h",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// own\n#include \"Button.h\"\n\nnamespace Material\n{\n\nclass Decoration;\n\nclass AppMenuButton : public Button\n{\n    Q_OBJECT\n\npublic:\n    AppMenuButton(Decoration *decoration, const int buttonIndex, QObject *parent = nullptr);\n    ~AppMenuButton() override;\n\n    Q_PROPERTY(int buttonIndex READ buttonIndex NOTIFY buttonIndexChanged)\n\n    int buttonIndex() const;\n\n    QColor backgroundColor() const override;\n    QColor foregroundColor() const override;\n\nsignals:\n    void buttonIndexChanged();\n\npublic slots:\n    virtual void trigger();\n\nprivate:\n    int m_buttonIndex;\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/AppMenuButtonGroup.cc",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2016 Kai Uwe Broulik <kde@privat.broulik.de>\n * Copyright (C) 2014 by Hugo Pereira Da Costa <hugo.pereira@free.fr>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n// own\n#include \"AppMenuButtonGroup.h\"\n#include \"Material.h\"\n#include \"BuildConfig.h\"\n#include \"AppMenuModel.h\"\n#include \"Decoration.h\"\n#include \"AppMenuButton.h\"\n#include \"TextButton.h\"\n#include \"MenuOverflowButton.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n#include <KDecoration2/DecorationButton>\n#include <KDecoration2/DecorationButtonGroup>\n\n// KF\n#include <KWindowSystem>\n\n// Qt\n#include <QAction>\n#include <QDebug>\n#include <QMenu>\n#include <QPainter>\n#include <QVariantAnimation>\n\n\nnamespace Material\n{\n\nAppMenuButtonGroup::AppMenuButtonGroup(Decoration *decoration)\n    : KDecoration2::DecorationButtonGroup(decoration)\n    , m_appMenuModel(nullptr)\n    , m_currentIndex(-1)\n    , m_overflowIndex(-1)\n    , m_hovered(false)\n    , m_showing(true)\n    , m_alwaysShow(true)\n    , m_animationEnabled(false)\n    , m_animation(new QVariantAnimation(this))\n    , m_opacity(1)\n{\n    // Assign showing and opacity before we bind the onShowingChanged animation\n    // so that new windows do not animate.\n    setAlwaysShow(decoration->menuAlwaysShow());\n    updateShowing();\n    setOpacity(m_showing ? 1 : 0);\n\n    connect(this, &AppMenuButtonGroup::showingChanged,\n            this, &AppMenuButtonGroup::onShowingChanged);\n    connect(this, &AppMenuButtonGroup::hoveredChanged,\n            this, &AppMenuButtonGroup::updateShowing);\n    connect(this, &AppMenuButtonGroup::alwaysShowChanged,\n            this, &AppMenuButtonGroup::updateShowing);\n    connect(this, &AppMenuButtonGroup::currentIndexChanged,\n            this, &AppMenuButtonGroup::updateShowing);\n\n    m_animationEnabled = decoration->animationsEnabled();\n    m_animation->setDuration(decoration->animationsDuration());\n    m_animation->setStartValue(0.0);\n    m_animation->setEndValue(1.0);\n    m_animation->setEasingCurve(QEasingCurve::InOutQuad);\n    connect(m_animation, &QVariantAnimation::valueChanged, this, [this](const QVariant &value) {\n        setOpacity(value.toReal());\n    });\n    connect(this, &AppMenuButtonGroup::opacityChanged, this, [this]() {\n        // update();\n    });\n\n    auto *decoratedClient = decoration->client().toStrongRef().data();\n    connect(decoratedClient, &KDecoration2::DecoratedClient::hasApplicationMenuChanged,\n            this, &AppMenuButtonGroup::updateAppMenuModel);\n    connect(this, &AppMenuButtonGroup::requestActivateIndex,\n            this, &AppMenuButtonGroup::trigger);\n    connect(this, &AppMenuButtonGroup::requestActivateOverflow,\n            this, &AppMenuButtonGroup::triggerOverflow);\n}\n\nAppMenuButtonGroup::~AppMenuButtonGroup()\n{\n}\n\nint AppMenuButtonGroup::currentIndex() const\n{\n    return m_currentIndex;\n}\n\nvoid AppMenuButtonGroup::setCurrentIndex(int set)\n{\n    if (m_currentIndex != set) {\n        m_currentIndex = set;\n        // qCDebug(category) << this << \"setCurrentIndex\" << m_currentIndex;\n        emit currentIndexChanged();\n    }\n}\n\nbool AppMenuButtonGroup::overflowing() const\n{\n    return m_overflowing;\n}\n\nvoid AppMenuButtonGroup::setOverflowing(bool set)\n{\n    if (m_overflowing != set) {\n        m_overflowing = set;\n        // qCDebug(category) << this << \"setOverflowing\" << m_overflowing;\n        emit overflowingChanged();\n    }\n}\n\nbool AppMenuButtonGroup::hovered() const\n{\n    return m_hovered;\n}\n\nvoid AppMenuButtonGroup::setHovered(bool value)\n{\n    if (m_hovered != value) {\n        m_hovered = value;\n        // qCDebug(category) << this << \"setHovered\" << m_hovered;\n        emit hoveredChanged(value);\n    }\n}\n\nbool AppMenuButtonGroup::showing() const\n{\n    return m_showing;\n}\n\nvoid AppMenuButtonGroup::setShowing(bool value)\n{\n    if (m_showing != value) {\n        m_showing = value;\n        // qCDebug(category) << this << \"setShowing\" << m_showing << \"alwaysShow\" << m_alwaysShow << \"currentIndex\" << m_currentIndex << \"opacity\" << m_opacity;\n        emit showingChanged(value);\n    }\n}\n\nbool AppMenuButtonGroup::alwaysShow() const\n{\n    return m_alwaysShow;\n}\n\nvoid AppMenuButtonGroup::setAlwaysShow(bool value)\n{\n    if (m_alwaysShow != value) {\n        m_alwaysShow = value;\n        // qCDebug(category) << this << \"setAlwaysShow\" << m_alwaysShow;\n        emit alwaysShowChanged(value);\n    }\n}\n\nbool AppMenuButtonGroup::animationEnabled() const\n{\n    return m_animationEnabled;\n}\n\nvoid AppMenuButtonGroup::setAnimationEnabled(bool value)\n{\n    if (m_animationEnabled != value) {\n        m_animationEnabled = value;\n        emit animationEnabledChanged(value);\n    }\n}\n\nint AppMenuButtonGroup::animationDuration() const\n{\n    return m_animation->duration();\n}\n\nvoid AppMenuButtonGroup::setAnimationDuration(int value)\n{\n    if (m_animation->duration() != value) {\n        m_animation->setDuration(value);\n        emit animationDurationChanged(value);\n    }\n}\n\nqreal AppMenuButtonGroup::opacity() const\n{\n    return m_opacity;\n}\n\nvoid AppMenuButtonGroup::setOpacity(qreal value)\n{\n    if (m_opacity != value) {\n        m_opacity = value;\n\n        for (int i = 0; i < buttons().length(); i++) {\n            KDecoration2::DecorationButton* decoButton = buttons().value(i);\n            auto *button = qobject_cast<Button *>(decoButton);\n            if (button) {\n                button->setOpacity(m_opacity);\n            }\n        }\n\n        emit opacityChanged(value);\n    }\n}\n\nKDecoration2::DecorationButton* AppMenuButtonGroup::buttonAt(int x, int y) const\n{\n    for (int i = 0; i < buttons().length(); i++) {\n        KDecoration2::DecorationButton* button = buttons().value(i);\n        if (!button->isVisible()) {\n            continue;\n        }\n        if (button->geometry().contains(x, y)) {\n            return button;\n        }\n    }\n    return nullptr;\n}\n\nvoid AppMenuButtonGroup::resetButtons()\n{\n    // qCDebug(category) << \"    resetButtons\";\n    // qCDebug(category) << \"        before\" << buttons();\n    auto list = QVector<QPointer<KDecoration2::DecorationButton>>(buttons());\n    // qCDebug(category) << \"          list\" << list;\n    removeButton(KDecoration2::DecorationButtonType::Custom);\n    // qCDebug(category) << \"     remCustom\" << buttons();\n    while (!list.isEmpty()) {\n        auto item = list.takeFirst();\n        // qCDebug(category) << \"        delete\" << item;\n        delete item;\n    }\n    // qCDebug(category) << \"         after\" << list;\n    emit menuUpdated();\n}\n\nvoid AppMenuButtonGroup::initAppMenuModel()\n{\n    m_appMenuModel = new AppMenuModel(this);\n    connect(m_appMenuModel, &AppMenuModel::modelReset,\n        this, &AppMenuButtonGroup::updateAppMenuModel);\n    // qCDebug(category) << \"AppMenuModel\" << m_appMenuModel;\n}\n\nvoid AppMenuButtonGroup::updateAppMenuModel()\n{\n    auto *deco = qobject_cast<Decoration *>(decoration());\n    if (!deco) {\n        return;\n    }\n    auto *decoratedClient = deco->client().toStrongRef().data();\n\n    // Don't display AppMenu in modal windows.\n    if (decoratedClient->isModal()) {\n        resetButtons();\n        return;\n    }\n\n    if (!decoratedClient->hasApplicationMenu()) {\n        resetButtons();\n        return;\n    }\n\n    if (m_appMenuModel) {\n        // Update AppMenuModel\n        // qCDebug(category) << \"AppMenuModel\" << m_appMenuModel;\n\n        resetButtons();\n\n        // Populate\n        for (int row = 0; row < m_appMenuModel->rowCount(); row++) {\n            const QModelIndex index = m_appMenuModel->index(row, 0);\n            const QString itemLabel = m_appMenuModel->data(index, AppMenuModel::MenuRole).toString();\n\n            // https://github.com/psifidotos/applet-window-appmenu/blob/908e60831d7d68ee56a56f9c24017a71822fc02d/lib/appmenuapplet.cpp#L167\n            const QVariant data = m_appMenuModel->data(index, AppMenuModel::ActionRole);\n            QAction *itemAction = (QAction *)data.value<void *>();\n\n            // qCDebug(category) << \"    \" << itemAction;\n\n            TextButton *b = new TextButton(deco, row, this);\n            b->setText(itemLabel);\n            b->setAction(itemAction);\n            b->setOpacity(m_opacity);\n\n            // Skip items with empty labels (The first item in a Gtk app)\n            if (itemLabel.isEmpty()) {\n                b->setEnabled(false);\n                b->setVisible(false);\n            }\n            \n            addButton(QPointer<KDecoration2::DecorationButton>(b));\n        }\n        m_overflowIndex = m_appMenuModel->rowCount();\n        addButton(new MenuOverflowButton(deco, m_overflowIndex, this));\n\n        emit menuUpdated();\n\n    } else {\n        // Init AppMenuModel\n        // qCDebug(category) << \"windowId\" << decoratedClient->windowId();\n        if (KWindowSystem::isPlatformX11()) {\n#if HAVE_X11\n            WId windowId = decoratedClient->windowId();\n            if (windowId != 0) {\n                initAppMenuModel();\n                m_appMenuModel->setWinId(windowId);\n                // qCDebug(category) << \"AppMenuModel\" << m_appMenuModel;\n            }\n#endif\n        } else if (KWindowSystem::isPlatformWayland()) {\n#if HAVE_Wayland\n            // TODO\n#endif\n        }\n    }\n}\n\nvoid AppMenuButtonGroup::updateOverflow(QRectF availableRect)\n{\n    // qCDebug(category) << \"updateOverflow\" << availableRect;\n    bool showOverflow = false;\n    for (KDecoration2::DecorationButton *button : buttons()) {\n        // qCDebug(category) << \"    \" << button->geometry() << button;\n        if (qobject_cast<MenuOverflowButton *>(button)) {\n            button->setVisible(showOverflow);\n            // qCDebug(category) << \"    showOverflow\" << showOverflow;\n        } else if (qobject_cast<TextButton *>(button)) {\n            if (button->isEnabled()) {\n                if (availableRect.contains(button->geometry())) {\n                    button->setVisible(true);\n                } else {\n                    button->setVisible(false);\n                    showOverflow = true;\n                }\n            }\n        }\n    }\n    setOverflowing(showOverflow);\n}\n\nvoid AppMenuButtonGroup::trigger(int buttonIndex) {\n    // qCDebug(category) << \"AppMenuButtonGroup::trigger\" << buttonIndex;\n    KDecoration2::DecorationButton* button = buttons().value(buttonIndex);\n\n    // https://github.com/psifidotos/applet-window-appmenu/blob/908e60831d7d68ee56a56f9c24017a71822fc02d/lib/appmenuapplet.cpp#L167\n    QMenu *actionMenu = nullptr;\n\n    if (buttonIndex == m_appMenuModel->rowCount()) {\n        // Overflow Menu\n        actionMenu = new QMenu();\n        actionMenu->setAttribute(Qt::WA_DeleteOnClose);\n\n        int overflowStartsAt = 0;\n        for (KDecoration2::DecorationButton *b : buttons()) {\n            TextButton* textButton = qobject_cast<TextButton *>(b);\n            if (textButton && textButton->isEnabled() && !textButton->isVisible()) {\n                overflowStartsAt = textButton->buttonIndex();\n                break;\n            }\n        }\n\n        QAction *action = nullptr;\n        for (int i = overflowStartsAt; i < m_appMenuModel->rowCount(); i++) {\n            const QModelIndex index = m_appMenuModel->index(i, 0);\n            const QVariant data = m_appMenuModel->data(index, AppMenuModel::ActionRole);\n            action = (QAction *)data.value<void *>();\n            actionMenu->addAction(action);\n        }\n\n    } else {\n        const QModelIndex modelIndex = m_appMenuModel->index(buttonIndex, 0);\n        const QVariant data = m_appMenuModel->data(modelIndex, AppMenuModel::ActionRole);\n        QAction *itemAction = (QAction *)data.value<void *>();\n        // qCDebug(category) << \"    action\" << itemAction;\n\n        if (itemAction) {\n            actionMenu = itemAction->menu();\n            // qCDebug(category) << \"    menu\" << actionMenu;\n        }\n    }\n\n    const auto *deco = qobject_cast<Decoration *>(decoration());\n    // if (actionMenu && deco) {\n    //     auto *decoratedClient = deco->client().toStrongRef().data();\n    //     actionMenu->setPalette(decoratedClient->palette());\n    // }\n\n    if (actionMenu && deco) {\n        QRectF buttonRect = button->geometry();\n        QPoint position = buttonRect.topLeft().toPoint();\n        QPoint rootPosition(position);\n        rootPosition += deco->windowPos();\n        // qCDebug(category) << \"    windowPos\" << windowPos;\n\n        // auto connection( QX11Info::connection() );\n\n        // button release event\n        // xcb_button_release_event_t releaseEvent;\n        // memset(&releaseEvent, 0, sizeof(releaseEvent));\n\n        // releaseEvent.response_type = XCB_BUTTON_RELEASE;\n        // releaseEvent.event =  windowId;\n        // releaseEvent.child = XCB_WINDOW_NONE;\n        // releaseEvent.root = QX11Info::appRootWindow();\n        // releaseEvent.event_x = position.x();\n        // releaseEvent.event_y = position.y();\n        // releaseEvent.root_x = rootPosition.x();\n        // releaseEvent.root_y = rootPosition.y();\n        // releaseEvent.detail = XCB_BUTTON_INDEX_1;\n        // releaseEvent.state = XCB_BUTTON_MASK_1;\n        // releaseEvent.time = XCB_CURRENT_TIME;\n        // releaseEvent.same_screen = true;\n        // xcb_send_event( connection, false, windowId, XCB_EVENT_MASK_BUTTON_RELEASE, reinterpret_cast<const char*>(&releaseEvent));\n\n        // xcb_ungrab_pointer( connection, XCB_TIME_CURRENT_TIME );\n        //---\n\n        actionMenu->installEventFilter(this);\n\n        if (!KWindowSystem::isPlatformWayland()) {\n            actionMenu->popup(rootPosition);\n        }\n\n        QMenu *oldMenu = m_currentMenu;\n        m_currentMenu = actionMenu;\n\n        if (oldMenu && oldMenu != actionMenu) {\n            // Don't reset the currentIndex when another menu is already shown\n            disconnect(oldMenu, &QMenu::aboutToHide, this, &AppMenuButtonGroup::onMenuAboutToHide);\n            oldMenu->hide();\n        }\n        if (0 <= m_currentIndex && m_currentIndex < buttons().length()) {\n            buttons().value(m_currentIndex)->setChecked(false);\n        }\n\n        if (KWindowSystem::isPlatformWayland()) {\n            actionMenu->popup(rootPosition);\n        }\n\n        setCurrentIndex(buttonIndex);\n        button->setChecked(true);\n\n        // FIXME TODO connect only once\n        connect(actionMenu, &QMenu::aboutToHide, this, &AppMenuButtonGroup::onMenuAboutToHide, Qt::UniqueConnection);\n    }\n}\n\nvoid AppMenuButtonGroup::triggerOverflow()\n{\n    // qCDebug(category) << \"AppMenuButtonGroup::triggerOverflow\" << m_overflowIndex;\n    trigger(m_overflowIndex);\n}\n\n// FIXME TODO doesn't work on submenu\nbool AppMenuButtonGroup::eventFilter(QObject *watched, QEvent *event)\n{\n    auto *menu = qobject_cast<QMenu *>(watched);\n\n    if (!menu) {\n        return false;\n    }\n\n    if (event->type() == QEvent::KeyPress) {\n        auto *e = static_cast<QKeyEvent *>(event);\n\n        // TODO right to left languages\n        if (e->key() == Qt::Key_Left) {\n            int desiredIndex = m_currentIndex - 1;\n            emit requestActivateIndex(desiredIndex);\n            return true;\n        } else if (e->key() == Qt::Key_Right) {\n            if (menu->activeAction() && menu->activeAction()->menu()) {\n                return false;\n            }\n\n            int desiredIndex = m_currentIndex + 1;\n            emit requestActivateIndex(desiredIndex);\n            return true;\n        }\n\n    } else if (event->type() == QEvent::MouseMove) {\n        auto *e = static_cast<QMouseEvent *>(event);\n\n        const auto *deco = qobject_cast<Decoration *>(decoration());\n\n        QPoint decoPos(e->globalPos());\n        decoPos -= deco->windowPos();\n        decoPos.ry() += deco->titleBarHeight();\n        // qCDebug(category) << \"MouseMove\";\n        // qCDebug(category) << \"       globalPos\" << e->globalPos();\n        // qCDebug(category) << \"       windowPos\" << deco->windowPos();\n        // qCDebug(category) << \"  titleBarHeight\" << deco->titleBarHeight();\n\n        KDecoration2::DecorationButton* item = buttonAt(decoPos.x(), decoPos.y());\n        if (!item) {\n            return false;\n        }\n\n        AppMenuButton* appMenuButton = qobject_cast<AppMenuButton *>(item);\n        if (appMenuButton) {\n            if (m_currentIndex != appMenuButton->buttonIndex()\n                && appMenuButton->isVisible()\n                && appMenuButton->isEnabled()\n            ) {\n                emit requestActivateIndex(appMenuButton->buttonIndex());\n            }\n            return false;\n        }\n    }\n\n    return false;\n}\n\nbool AppMenuButtonGroup::isMenuOpen() const\n{\n    return 0 <= m_currentIndex;\n}\n\nvoid AppMenuButtonGroup::unPressAllButtons()\n{\n    // qCDebug(category) << \"AppMenuButtonGroup::unPressAllButtons\";\n    for (int i = 0; i < buttons().length(); i++) {\n        KDecoration2::DecorationButton* button = buttons().value(i);\n\n        // Hack to setPressed(false)\n        button->setEnabled(!button->isEnabled());\n        button->setEnabled(!button->isEnabled());\n    }\n}\n\nvoid AppMenuButtonGroup::updateShowing()\n{\n    setShowing(m_alwaysShow || m_hovered || isMenuOpen());\n}\n\nvoid AppMenuButtonGroup::onMenuAboutToHide()\n{\n    if (0 <= m_currentIndex && m_currentIndex < buttons().length()) {\n        buttons().value(m_currentIndex)->setChecked(false);\n    }\n    setCurrentIndex(-1);\n}\n\nvoid AppMenuButtonGroup::onShowingChanged(bool showing)\n{\n    if (m_animationEnabled) {\n        QAbstractAnimation::Direction dir = showing ? QAbstractAnimation::Forward : QAbstractAnimation::Backward;\n        if (m_animation->state() == QAbstractAnimation::Running && m_animation->direction() != dir) {\n            m_animation->stop();\n        }\n        m_animation->setDirection(dir);\n        if (m_animation->state() != QAbstractAnimation::Running) {\n            m_animation->start();\n        }\n    } else {\n        setOpacity(showing ? 1 : 0);\n    }\n}\n\n} // namespace Material\n"
  },
  {
    "path": "src/AppMenuButtonGroup.h",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// own\n#include \"AppMenuModel.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n#include <KDecoration2/DecorationButton>\n#include <KDecoration2/DecorationButtonGroup>\n\n// Qt\n#include <QMenu>\n#include <QVariantAnimation>\n\nnamespace Material\n{\n\nclass Decoration;\n\nclass AppMenuButtonGroup : public KDecoration2::DecorationButtonGroup\n{\n    Q_OBJECT\n\npublic:\n    AppMenuButtonGroup(Decoration *decoration);\n    ~AppMenuButtonGroup() override;\n\n    Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)\n    Q_PROPERTY(int overflowing READ overflowing WRITE setOverflowing NOTIFY overflowingChanged)\n    Q_PROPERTY(bool hovered READ hovered WRITE setHovered NOTIFY hoveredChanged)\n    Q_PROPERTY(bool showing READ showing WRITE setShowing NOTIFY showingChanged)\n    Q_PROPERTY(bool alwaysShow READ alwaysShow WRITE setAlwaysShow NOTIFY alwaysShowChanged)\n    Q_PROPERTY(bool animationEnabled READ animationEnabled WRITE setAnimationEnabled NOTIFY animationEnabledChanged)\n    Q_PROPERTY(int animationDuration READ animationDuration WRITE setAnimationDuration NOTIFY animationDurationChanged)\n    Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged)\n\n    int currentIndex() const;\n    void setCurrentIndex(int set);\n\n    bool overflowing() const;\n    void setOverflowing(bool set);\n\n    bool hovered() const;\n    void setHovered(bool value);\n\n    bool showing() const;\n    void setShowing(bool value);\n\n    bool alwaysShow() const;\n    void setAlwaysShow(bool value);\n\n    bool animationEnabled() const;\n    void setAnimationEnabled(bool value);\n\n    int animationDuration() const;\n    void setAnimationDuration(int duration);\n\n    qreal opacity() const;\n    void setOpacity(qreal value);\n\n    bool isMenuOpen() const;\n\n    KDecoration2::DecorationButton* buttonAt(int x, int y) const;\n\n    void unPressAllButtons();\n\npublic slots:\n    void initAppMenuModel();\n    void updateAppMenuModel();\n    void updateOverflow(QRectF availableRect);\n    void trigger(int index);\n    void triggerOverflow();\n    void updateShowing();\n    void onMenuAboutToHide();\n\nprivate slots:\n    void onShowingChanged(bool hovered);\n\nsignals:\n    void menuUpdated();\n    void requestActivateIndex(int index);\n    void requestActivateOverflow();\n\n    void currentIndexChanged();\n    void overflowingChanged();\n    void hoveredChanged(bool);\n    void showingChanged(bool);\n    void alwaysShowChanged(bool);\n    void animationEnabledChanged(bool);\n    void animationDurationChanged(int);\n    void opacityChanged(qreal);\n\nprotected:\n    bool eventFilter(QObject *watched, QEvent *event) override;\n\nprivate:\n    void resetButtons();\n\n    AppMenuModel *m_appMenuModel;\n    int m_currentIndex;\n    int m_overflowIndex;\n    bool m_overflowing;\n    bool m_hovered;\n    bool m_showing;\n    bool m_alwaysShow;\n    bool m_animationEnabled;\n    QVariantAnimation *m_animation;\n    qreal m_opacity;\n    QPointer<QMenu> m_currentMenu;\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/AppMenuModel.cc",
    "content": "/******************************************************************\n * Copyright 2016 Kai Uwe Broulik <kde@privat.broulik.de>\n * Copyright 2016 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License or (at your option) version 3 or any later version\n * accepted by the membership of KDE e.V. (or its successor approved\n * by the membership of KDE e.V.), which shall act as a proxy\n * defined in Section 14 of version 3 of the license.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n *\n ******************************************************************/\n\n// Based on:\n// https://invent.kde.org/plasma/plasma-workspace/-/blob/master/applets/appmenu/plugin/appmenumodel.cpp\n// https://github.com/psifidotos/applet-window-appmenu/blob/master/plugin/appmenumodel.cpp\n\n// own\n#include \"AppMenuModel.h\"\n#include \"Material.h\"\n#include \"BuildConfig.h\"\n\n// KF\n#include <KWindowSystem>\n// In KF5 5.101, KWindowSystem moved several signals to KX11Extras\n// Eg: https://invent.kde.org/frameworks/kwindowsystem/-/commit/7cfd7c36eb017242d7a0202db82895be6b8fb81c\n#if HAVE_KF5_101 // KX11Extras\n#include <KX11Extras>\n#endif\n\n// Qt\n#include <QAction>\n#include <QDebug>\n#include <QMenu>\n#include <QDBusConnection>\n#include <QDBusConnectionInterface>\n#include <QDBusServiceWatcher>\n#include <QGuiApplication>\n\n// libdbusmenuqt\n#include <dbusmenuimporter.h>\n\n#if HAVE_X11\n#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)\n#include <private/qtx11extras_p.h>\n#else\n#include <QX11Info>\n#endif\n#include <xcb/xcb.h>\n#endif\n\n\nnamespace Material\n{\n\nstatic const QByteArray s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral(\"_KDE_NET_WM_APPMENU_SERVICE_NAME\");\nstatic const QByteArray s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral(\"_KDE_NET_WM_APPMENU_OBJECT_PATH\");\n\n#if HAVE_X11\nstatic QHash<QByteArray, xcb_atom_t> s_atoms;\n#endif\n\nclass KDBusMenuImporter : public DBusMenuImporter\n{\n\npublic:\n    KDBusMenuImporter(const QString &service, const QString &path, QObject *parent)\n        : DBusMenuImporter(service, path, parent) {\n\n    }\n\nprotected:\n    QIcon iconForName(const QString &name) override {\n        return QIcon::fromTheme(name);\n    }\n\n};\n\nAppMenuModel::AppMenuModel(QObject *parent)\n    : QAbstractListModel(parent),\n      m_serviceWatcher(new QDBusServiceWatcher(this))\n{\n    if (KWindowSystem::isPlatformX11()) {\n#if HAVE_X11\n        x11Init();\n#else\n        // Not compiled with X11\n        return;\n#endif\n\n    } else if (KWindowSystem::isPlatformWayland()) {\n#if HAVE_Wayland\n        // TODO\n        // waylandInit();\n        return;\n#else\n        // Not compiled with KWayland\n        return;\n#endif\n\n    } else {\n        // Not X11 or Wayland\n        return;\n    }\n\n    m_serviceWatcher->setConnection(QDBusConnection::sessionBus());\n    // If our current DBus connection gets lost, close the menu\n    // we'll select the new menu when the focus changes\n    connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString & serviceName) {\n        if (serviceName == m_serviceName) {\n            setMenuAvailable(false);\n            emit modelNeedsUpdate();\n        }\n    });\n}\n\nAppMenuModel::~AppMenuModel() = default;\n\nvoid AppMenuModel::x11Init()\n{\n#if HAVE_X11\n    connect(this, &AppMenuModel::winIdChanged,\n            this, &AppMenuModel::onWinIdChanged);\n\n// In KF5 5.101, KWindowSystem moved several signals to KX11Extras\n// Eg: https://invent.kde.org/frameworks/kwindowsystem/-/commit/7cfd7c36eb017242d7a0202db82895be6b8fb81c\n#if HAVE_KF5_101 // KX11Extras\n    // Select non-deprecated overloaded method. Uses coding pattern from:\n    // https://invent.kde.org/plasma/plasma-workspace/blame/master/libtaskmanager/xwindowsystemeventbatcher.cpp#L30\n    void (KX11Extras::*myWindowChangeSignal)(WId window, NET::Properties properties, NET::Properties2 properties2) = &KX11Extras::windowChanged;\n    connect(KX11Extras::self(), myWindowChangeSignal,\n            this, &AppMenuModel::onX11WindowChanged);\n\n    // There are apps that are not releasing their menu properly after closing\n    // and as such their menu is still shown even though the app does not exist\n    // any more. Such apps are Java based e.g. smartgit\n    connect(KX11Extras::self(), &KX11Extras::windowRemoved,\n            this, &AppMenuModel::onX11WindowRemoved);\n#else // KF5 5.100 KWindowSystem\n    void (KWindowSystem::*myWindowChangeSignal)(WId window, NET::Properties properties, NET::Properties2 properties2) = &KWindowSystem::windowChanged;\n    connect(KWindowSystem::self(), myWindowChangeSignal,\n            this, &AppMenuModel::onX11WindowChanged);\n    connect(KWindowSystem::self(), &KWindowSystem::windowRemoved,\n            this, &AppMenuModel::onX11WindowRemoved);\n#endif\n\n    connect(this, &AppMenuModel::modelNeedsUpdate, this, [this] {\n        if (!m_updatePending) {\n            m_updatePending = true;\n            QMetaObject::invokeMethod(this, \"update\", Qt::QueuedConnection);\n        }\n    });\n#endif\n}\n\nvoid AppMenuModel::waylandInit()\n{\n#if HAVE_Wayland\n    // TODO\n#endif\n}\n\nbool AppMenuModel::menuAvailable() const\n{\n    return m_menuAvailable;\n}\n\nvoid AppMenuModel::setMenuAvailable(bool set)\n{\n    if (m_menuAvailable != set) {\n        m_menuAvailable = set;\n        emit menuAvailableChanged();\n    }\n}\n\nQVariant AppMenuModel::winId() const\n{\n    return m_winId;\n}\n\nvoid AppMenuModel::setWinId(const QVariant &id)\n{\n    if (m_winId == id) {\n        return;\n    }\n    qCDebug(category) << \"AppMenuModel::setWinId\" << m_winId << \" => \" << id;\n    m_winId = id;\n    emit winIdChanged();\n}\n\nint AppMenuModel::rowCount(const QModelIndex &parent) const\n{\n    Q_UNUSED(parent);\n\n    if (!m_menuAvailable || !m_menu) {\n        return 0;\n    }\n\n    return m_menu->actions().count();\n}\n\nvoid AppMenuModel::update()\n{\n    // qCDebug(category) << \"AppMenuModel::update (\" << m_winId << \")\";\n    beginResetModel();\n    endResetModel();\n    m_updatePending = false;\n}\n\n\nvoid AppMenuModel::onWinIdChanged()\n{\n\n    if (KWindowSystem::isPlatformX11()) {\n#if HAVE_X11\n\n        qApp->removeNativeEventFilter(this);\n\n        const WId id = m_winId.toUInt();\n\n        if (!id) {\n            setMenuAvailable(false);\n            emit modelNeedsUpdate();\n            return;\n        }\n\n        auto *c = QX11Info::connection();\n\n        auto getWindowPropertyString = [c](WId id, const QByteArray &name) -> QByteArray {\n            QByteArray value;\n\n            if (!s_atoms.contains(name))\n            {\n                const xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom(c, false, name.length(), name.constData());\n                QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> atomReply(xcb_intern_atom_reply(c, atomCookie, nullptr));\n\n                if (atomReply.isNull()) {\n                    return value;\n                }\n\n                s_atoms[name] = atomReply->atom;\n\n                if (s_atoms[name] == XCB_ATOM_NONE) {\n                    return value;\n                }\n            }\n\n            static const long MAX_PROP_SIZE = 10000;\n            auto propertyCookie = xcb_get_property(c, false, id, s_atoms[name], XCB_ATOM_STRING, 0, MAX_PROP_SIZE);\n            QScopedPointer<xcb_get_property_reply_t, QScopedPointerPodDeleter> propertyReply(xcb_get_property_reply(c, propertyCookie, nullptr));\n\n            if (propertyReply.isNull())\n            {\n                return value;\n            }\n\n            if (propertyReply->type == XCB_ATOM_STRING && propertyReply->format == 8 && propertyReply->value_len > 0)\n            {\n                const char *data = (const char *) xcb_get_property_value(propertyReply.data());\n                int len = propertyReply->value_len;\n\n                if (data) {\n                    value = QByteArray(data, data[len - 1] ? len : len - 1);\n                }\n            }\n\n            return value;\n        };\n\n        auto updateMenuFromWindowIfHasMenu = [this, &getWindowPropertyString](WId id) {\n            const QString serviceName = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuServiceNamePropertyName));\n            const QString menuObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuObjectPathPropertyName));\n\n            if (!serviceName.isEmpty() && !menuObjectPath.isEmpty()) {\n                updateApplicationMenu(serviceName, menuObjectPath);\n                return true;\n            }\n\n            return false;\n        };\n\n        if (updateMenuFromWindowIfHasMenu(id)) {\n            return;\n        }\n\n        // monitor whether an app menu becomes available later\n        // this can happen when an app starts, shows its window, and only later announces global menu (e.g. Firefox)\n        qApp->installNativeEventFilter(this);\n        m_delayedMenuWindowId = id;\n\n        //no menu found, set it to unavailable\n        setMenuAvailable(false);\n        emit modelNeedsUpdate();\n#endif\n\n    } else if (KWindowSystem::isPlatformWayland()) {\n#if HAVE_Wayland\n        // TODO\n#endif\n    }\n}\n\nvoid AppMenuModel::onX11WindowChanged(WId id)\n{\n    if (m_winId.toUInt() == id) {\n        \n    }\n}\n\nvoid AppMenuModel::onX11WindowRemoved(WId id)\n{\n    if (m_winId.toUInt() == id) {\n        setMenuAvailable(false);\n    }\n}\n\nQHash<int, QByteArray> AppMenuModel::roleNames() const\n{\n    QHash<int, QByteArray> roleNames;\n    roleNames[MenuRole] = QByteArrayLiteral(\"activeMenu\");\n    roleNames[ActionRole] = QByteArrayLiteral(\"activeActions\");\n    return roleNames;\n}\n\nQVariant AppMenuModel::data(const QModelIndex &index, int role) const\n{\n    const int row = index.row();\n\n    if (row < 0 || !m_menuAvailable || !m_menu) {\n        return QVariant();\n    }\n\n    const auto actions = m_menu->actions();\n\n    if (row >= actions.count()) {\n        return QVariant();\n    }\n\n    if (role == MenuRole) { // TODO this should be Qt::DisplayRole\n        return actions.at(row)->text();\n    } else if (role == ActionRole) {\n        return QVariant::fromValue((void *) actions.at(row));\n    }\n\n    return QVariant();\n}\n\nvoid AppMenuModel::updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath)\n{\n    if (m_serviceName == serviceName && m_menuObjectPath == menuObjectPath) {\n        if (m_importer) {\n            QMetaObject::invokeMethod(m_importer, \"updateMenu\", Qt::QueuedConnection);\n        }\n        return;\n    }\n\n    m_serviceName = serviceName;\n    m_serviceWatcher->setWatchedServices(QStringList({m_serviceName}));\n\n    m_menuObjectPath = menuObjectPath;\n\n    if (m_importer) {\n        m_importer->deleteLater();\n    }\n\n    m_importer = new KDBusMenuImporter(serviceName, menuObjectPath, this);\n    QMetaObject::invokeMethod(m_importer, \"updateMenu\", Qt::QueuedConnection);\n\n    connect(m_importer.data(), &DBusMenuImporter::menuUpdated, this, [=](QMenu *menu) {\n        m_menu = m_importer->menu();\n        if (m_menu.isNull() || menu != m_menu) {\n            return;\n        }\n\n        // cache first layer of sub menus, which we'll be popping up\n        const auto actions = m_menu->actions();\n        for (QAction *a : actions) {\n            // signal dataChanged when the action changes\n            connect(a, &QAction::changed, this, [this, a] {\n                if (m_menuAvailable && m_menu) {\n                    const int actionIdx = m_menu->actions().indexOf(a);\n                    if (actionIdx > -1) {\n                        const QModelIndex modelIdx = index(actionIdx, 0);\n                        emit dataChanged(modelIdx, modelIdx);\n                    }\n                }\n            });\n\n            connect(a, &QAction::destroyed, this, &AppMenuModel::modelNeedsUpdate);\n\n            if (a->menu()) {\n                m_importer->updateMenu(a->menu());\n            }\n        }\n\n        setMenuAvailable(true);\n        emit modelNeedsUpdate();\n    });\n\n    connect(m_importer.data(), &DBusMenuImporter::actionActivationRequested, this, [this](QAction *action) {\n        // TODO submenus\n        if (!m_menuAvailable || !m_menu) {\n            return;\n        }\n\n        const auto actions = m_menu->actions();\n        auto it = std::find(actions.begin(), actions.end(), action);\n        if (it != actions.end()) {\n            emit requestActivateIndex(it - actions.begin());\n        }\n    });\n}\n\nbool AppMenuModel::nativeEventFilter(const QByteArray &eventType, void *message, long *result)\n{\n    Q_UNUSED(result);\n\n    if (!KWindowSystem::isPlatformX11() || eventType != \"xcb_generic_event_t\") {\n        return false;\n    }\n\n#if HAVE_X11\n    auto e = static_cast<xcb_generic_event_t *>(message);\n    const uint8_t type = e->response_type & ~0x80;\n\n    if (type == XCB_PROPERTY_NOTIFY) {\n        auto *event = reinterpret_cast<xcb_property_notify_event_t *>(e);\n\n        if (event->window == m_delayedMenuWindowId) {\n\n            auto serviceNameAtom = s_atoms.value(s_x11AppMenuServiceNamePropertyName);\n            auto objectPathAtom = s_atoms.value(s_x11AppMenuObjectPathPropertyName);\n\n            if (serviceNameAtom != XCB_ATOM_NONE && objectPathAtom != XCB_ATOM_NONE) { // shouldn't happen\n                if (event->atom == serviceNameAtom || event->atom == objectPathAtom) {\n                    // see if we now have a menu\n                    onWinIdChanged();\n                }\n            }\n        }\n    }\n\n#else\n    Q_UNUSED(message);\n#endif\n\n    return false;\n}\n\n} // namespace Material\n"
  },
  {
    "path": "src/AppMenuModel.h",
    "content": "/******************************************************************\n * Copyright 2016 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License or (at your option) version 3 or any later version\n * accepted by the membership of KDE e.V. (or its successor approved\n * by the membership of KDE e.V.), which shall act as a proxy\n * defined in Section 14 of version 3 of the license.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n *\n ******************************************************************/\n\n#pragma once\n\n// Qt\n#include <QAbstractListModel>\n#include <QAbstractNativeEventFilter>\n#include <QAction>\n#include <QDBusServiceWatcher>\n#include <QMenu>\n#include <QModelIndex>\n#include <QPointer>\n#include <QRect>\n#include <QStringList>\n\n\nnamespace Material\n{\n\nclass KDBusMenuImporter;\n\nclass AppMenuModel : public QAbstractListModel, public QAbstractNativeEventFilter\n{\n    Q_OBJECT\n\n    Q_PROPERTY(bool menuAvailable READ menuAvailable WRITE setMenuAvailable NOTIFY menuAvailableChanged)\n    Q_PROPERTY(QVariant winId READ winId WRITE setWinId NOTIFY winIdChanged)\n\npublic:\n    explicit AppMenuModel(QObject *parent = nullptr);\n    ~AppMenuModel() override;\n\nprivate:\n    void x11Init();\n    void waylandInit();\n\npublic:\n    enum AppMenuRole\n    {\n        MenuRole = Qt::UserRole + 1, // TODO this should be Qt::DisplayRole\n        ActionRole\n    };\n\n    QVariant data(const QModelIndex &index, int role) const override;\n    int rowCount(const QModelIndex &parent = QModelIndex()) const override;\n    QHash<int, QByteArray> roleNames() const override;\n\n    void updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath);\n\n    bool menuAvailable() const;\n    void setMenuAvailable(bool set);\n\n    QVariant winId() const;\n    void setWinId(const QVariant &id);\n\nsignals:\n    void requestActivateIndex(int index);\n\nprotected:\n    bool nativeEventFilter(const QByteArray &eventType, void *message, long int *result) override;\n\nprivate Q_SLOTS:\n    void onWinIdChanged();\n    void onX11WindowChanged(WId id);\n    void onX11WindowRemoved(WId id);\n\n    void update();\n\nsignals:\n    void menuAvailableChanged();\n    void modelNeedsUpdate();\n    void winIdChanged();\n\nprivate:\n    bool m_menuAvailable;\n    bool m_updatePending = false;\n\n    QVariant m_winId{-1};\n\n    //! window that its menu initialization may be delayed\n    WId m_delayedMenuWindowId = 0;\n\n    QPointer<QMenu> m_menu;\n\n    QDBusServiceWatcher *m_serviceWatcher;\n    QString m_serviceName;\n    QString m_menuObjectPath;\n\n    QPointer<KDBusMenuImporter> m_importer;\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/ApplicationMenuButton.h",
    "content": "/*\n * Copyright (C) 2019 Zain Ahmad <zain.x.ahmad@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// own\n#include \"Button.h\"\n#include \"Material.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n\n// Qt\n#include <QPainter>\n\nnamespace Material\n{\n\nclass ApplicationMenuButton\n{\n\npublic:\n    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {\n        button->setVisible(decoratedClient->hasApplicationMenu());\n    }\n    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {\n        button->setPenWidth(painter, gridUnit, 1.75);\n\n        int spacing = qRound(gridUnit * 4);\n        for (int i = -1; i <= 1; ++i) {\n            const QPointF left { iconRect.left(), iconRect.center().y() + i * spacing };\n            const QPointF right { iconRect.right(), iconRect.center().y() + i * spacing };\n\n            painter->drawLine(left, right);\n        }\n    }\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/BoxShadowHelper.cc",
    "content": "/*\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n// own\n#include \"BoxShadowHelper.h\"\n\n// Qt\n#include <QVector>\n\n// std\n#include <cmath>\n\n\nnamespace Material\n{\nnamespace BoxShadowHelper\n{\n\nnamespace\n{\n// According to the CSS Level 3 spec, standard deviation must be equal to\n// half of the blur radius. https://www.w3.org/TR/css-backgrounds-3/#shadow-blur\n// Current window size is too small for sigma equal to half of the blur radius.\n// As a workaround, sigma blur scale is lowered. With the lowered sigma\n// blur scale, area under the kernel equals to 0.98, which is pretty enough.\n// Maybe, it should be changed in the future.\nconst qreal SIGMA_BLUR_SCALE = 0.4375;\n} // anonymous namespace\n\ninline qreal radiusToSigma(qreal radius)\n{\n    return radius * SIGMA_BLUR_SCALE;\n}\n\ninline int boxSizeToRadius(int boxSize)\n{\n    return (boxSize - 1) / 2;\n}\n\nQVector<int> computeBoxSizes(int radius, int numIterations)\n{\n    const qreal sigma = radiusToSigma(radius);\n\n    // Box sizes are computed according to the \"Fast Almost-Gaussian Filtering\"\n    // paper by Peter Kovesi.\n    int lower = std::floor(std::sqrt(12 * std::pow(sigma, 2) / numIterations + 1));\n    if (lower % 2 == 0) {\n        lower--;\n    }\n\n    const int upper = lower + 2;\n    const int threshold = std::round((12 * std::pow(sigma, 2) - numIterations * std::pow(lower, 2)\n        - 4 * numIterations * lower - 3 * numIterations) / (-4 * lower - 4));\n\n    QVector<int> boxSizes;\n    boxSizes.reserve(numIterations);\n    for (int i = 0; i < numIterations; ++i) {\n        boxSizes.append(i < threshold ? lower : upper);\n    }\n\n    return boxSizes;\n}\n\nvoid boxBlurPass(const QImage &src, QImage &dst, int boxSize)\n{\n    const int alphaStride = src.depth() >> 3;\n    const int alphaOffset = QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3;\n\n    const int radius = boxSizeToRadius(boxSize);\n    const qreal invSize = 1.0 / boxSize;\n\n    const int dstStride = dst.width() * alphaStride;\n\n    for (int y = 0; y < src.height(); ++y) {\n        const uchar *srcAlpha = src.scanLine(y);\n        uchar *dstAlpha = dst.scanLine(0);\n\n        srcAlpha += alphaOffset;\n        dstAlpha += alphaOffset + y * alphaStride;\n\n        const uchar *left = srcAlpha;\n        const uchar *right = left + alphaStride * radius;\n\n        int window = 0;\n        for (int x = 0; x < radius; ++x) {\n            window += *srcAlpha;\n            srcAlpha += alphaStride;\n        }\n\n        for (int x = 0; x <= radius; ++x) {\n            window += *right;\n            right += alphaStride;\n            *dstAlpha = static_cast<uchar>(window * invSize);\n            dstAlpha += dstStride;\n        }\n\n        for (int x = radius + 1; x < src.width() - radius; ++x) {\n            window += *right - *left;\n            left += alphaStride;\n            right += alphaStride;\n            *dstAlpha = static_cast<uchar>(window * invSize);\n            dstAlpha += dstStride;\n        }\n\n        for (int x = src.width() - radius; x < src.width(); ++x) {\n            window -= *left;\n            left += alphaStride;\n            *dstAlpha = static_cast<uchar>(window * invSize);\n            dstAlpha += dstStride;\n        }\n    }\n}\n\nvoid boxBlurAlpha(QImage &image, int radius, int numIterations)\n{\n    // Temporary buffer is transposed so we always read memory\n    // in linear order.\n    QImage tmp(image.height(), image.width(), image.format());\n\n    const QVector<int> boxSizes = computeBoxSizes(radius, numIterations);\n    for (const int &boxSize : boxSizes) {\n        boxBlurPass(image, tmp, boxSize); // horizontal pass\n        boxBlurPass(tmp, image, boxSize); // vertical pass\n    }\n}\n\nvoid boxShadow(QPainter *p, const QRect &box, const QPoint &offset, int radius, const QColor &color)\n{\n    const QSize size = box.size() + 2 * QSize(radius, radius);\n    const qreal dpr = p->device()->devicePixelRatioF();\n\n    QPainter painter;\n\n    QImage shadow(size * dpr, QImage::Format_ARGB32_Premultiplied);\n    shadow.setDevicePixelRatio(dpr);\n    shadow.fill(Qt::transparent);\n\n    painter.begin(&shadow);\n    painter.fillRect(QRect(QPoint(radius, radius), box.size()), Qt::black);\n    painter.end();\n\n    // There is no need to blur RGB channels. Blur the alpha\n    // channel and then give the shadow a tint of the desired color.\n    const int numIterations = 3;\n    boxBlurAlpha(shadow, radius, numIterations);\n\n    painter.begin(&shadow);\n    painter.setCompositionMode(QPainter::CompositionMode_SourceIn);\n    painter.fillRect(shadow.rect(), color);\n    painter.end();\n\n    QRect shadowRect = shadow.rect();\n    shadowRect.setSize(shadowRect.size() / dpr);\n    shadowRect.moveCenter(box.center() + offset);\n    p->drawImage(shadowRect, shadow);\n}\n\n} // namespace BoxShadowHelper\n} // namespace Material\n"
  },
  {
    "path": "src/BoxShadowHelper.h",
    "content": "/*\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// Qt\n#include <QColor>\n#include <QPainter>\n#include <QPoint>\n#include <QRect>\n\nnamespace Material\n{\nnamespace BoxShadowHelper\n{\n\nvoid boxShadow(QPainter *p, const QRect &box, const QPoint &offset,\n               int radius, const QColor &color);\n\n} // namespace BoxShadowHelper\n} // namespace Material\n"
  },
  {
    "path": "src/BuildConfig.h.cmake",
    "content": "#cmakedefine01 HAVE_Wayland\n#cmakedefine01 HAVE_X11\n#cmakedefine01 HAVE_KDecoration2_5_25\n#cmakedefine01 HAVE_KF5_101\n"
  },
  {
    "path": "src/Button.cc",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n// own\n#include \"Button.h\"\n#include \"Material.h\"\n#include \"Decoration.h\"\n\n#include \"AppIconButton.h\"\n#include \"ApplicationMenuButton.h\"\n#include \"OnAllDesktopsButton.h\"\n#include \"ContextHelpButton.h\"\n#include \"ShadeButton.h\"\n#include \"KeepAboveButton.h\"\n#include \"KeepBelowButton.h\"\n#include \"CloseButton.h\"\n#include \"MaximizeButton.h\"\n#include \"MinimizeButton.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n#include <KDecoration2/Decoration>\n#include <KDecoration2/DecorationButton>\n\n// KF\n#include <KColorUtils>\n\n// Qt\n#include <QDebug>\n#include <QMargins>\n#include <QPainter>\n#include <QVariantAnimation>\n#include <QtMath> // qFloor\n\n\nnamespace Material\n{\n\nButton::Button(KDecoration2::DecorationButtonType type, Decoration *decoration, QObject *parent)\n    : DecorationButton(type, decoration, parent)\n    , m_animationEnabled(true)\n    , m_animation(new QVariantAnimation(this))\n    , m_opacity(1)\n    , m_transitionValue(0)\n    , m_padding(new QMargins())\n    , m_isGtkButton(false)\n{\n    connect(this, &Button::hoveredChanged, this,\n        [this](bool hovered) {\n            updateAnimationState(hovered);\n            update();\n        });\n\n    if (QCoreApplication::applicationName() == QStringLiteral(\"kded5\")) {\n        // See: https://github.com/Zren/material-decoration/issues/22\n        // kde-gtk-config has a kded5 module which renders the buttons to svgs for gtk.\n        m_isGtkButton = true;\n    }\n\n    // Animation based on SierraBreezeEnhanced\n    // https://github.com/kupiqu/SierraBreezeEnhanced/blob/master/breezebutton.cpp#L45\n    // The GTK bridge needs animations disabled to render hover states. See Issue #50.\n    // https://invent.kde.org/plasma/kde-gtk-config/-/blob/master/kded/kwin_bridge/dummydecorationbridge.cpp#L35\n    m_animationEnabled = !m_isGtkButton && decoration->animationsEnabled();\n    m_animation->setDuration(decoration->animationsDuration());\n    m_animation->setStartValue(0.0);\n    m_animation->setEndValue(1.0);\n    m_animation->setEasingCurve(QEasingCurve::InOutQuad);\n    connect(m_animation, &QVariantAnimation::valueChanged, this, [this](const QVariant &value) {\n        setTransitionValue(value.toReal());\n    });\n    connect(this, &Button::transitionValueChanged, this, [this]() {\n        update();\n    });\n\n    connect(this, &Button::opacityChanged, this, [this]() {\n        update();\n    });\n\n    setHeight(decoration->titleBarHeight());\n\n    auto *decoratedClient = decoration->client().toStrongRef().data();\n\n    switch (type) {\n    case KDecoration2::DecorationButtonType::Menu:\n        AppIconButton::init(this, decoratedClient);\n        break;\n\n    case KDecoration2::DecorationButtonType::ApplicationMenu:\n        ApplicationMenuButton::init(this, decoratedClient);\n        break;\n\n    case KDecoration2::DecorationButtonType::OnAllDesktops:\n        OnAllDesktopsButton::init(this, decoratedClient);\n        break;\n\n    case KDecoration2::DecorationButtonType::ContextHelp:\n        ContextHelpButton::init(this, decoratedClient);\n        break;\n\n    case KDecoration2::DecorationButtonType::Shade:\n        ShadeButton::init(this, decoratedClient);\n        break;\n\n    case KDecoration2::DecorationButtonType::KeepAbove:\n        KeepAboveButton::init(this, decoratedClient);\n        break;\n\n    case KDecoration2::DecorationButtonType::KeepBelow:\n        KeepBelowButton::init(this, decoratedClient);\n        break;\n\n    case KDecoration2::DecorationButtonType::Close:\n        CloseButton::init(this, decoratedClient);\n        break;\n\n    case KDecoration2::DecorationButtonType::Maximize:\n        MaximizeButton::init(this, decoratedClient);\n        break;\n\n    case KDecoration2::DecorationButtonType::Minimize:\n        MinimizeButton::init(this, decoratedClient);\n        break;\n\n    default:\n        break;\n    }\n}\n\nButton::~Button()\n{\n}\n\nKDecoration2::DecorationButton* Button::create(KDecoration2::DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent)\n{\n    auto deco = qobject_cast<Decoration*>(decoration);\n    if (!deco) {\n        return nullptr;\n    }\n\n    switch (type) {\n    case KDecoration2::DecorationButtonType::Menu:\n    // case KDecoration2::DecorationButtonType::ApplicationMenu:\n    case KDecoration2::DecorationButtonType::OnAllDesktops:\n    case KDecoration2::DecorationButtonType::ContextHelp:\n    case KDecoration2::DecorationButtonType::Shade:\n    case KDecoration2::DecorationButtonType::KeepAbove:\n    case KDecoration2::DecorationButtonType::KeepBelow:\n    case KDecoration2::DecorationButtonType::Close:\n    case KDecoration2::DecorationButtonType::Maximize:\n    case KDecoration2::DecorationButtonType::Minimize:\n        return new Button(type, deco, parent);\n\n    default:\n        return nullptr;\n    }\n}\n\nButton::Button(QObject *parent, const QVariantList &args)\n    : Button(args.at(0).value<KDecoration2::DecorationButtonType>(), args.at(1).value<Decoration*>(), parent)\n{\n}\n\nvoid Button::paint(QPainter *painter, const QRect &repaintRegion)\n{\n    Q_UNUSED(repaintRegion)\n\n    // Buttons are coded assuming 24 units in size.\n    const QRectF buttonRect = geometry();\n    const QRectF contentRect = contentArea();\n\n    const qreal iconScale = contentRect.height()/24;\n    int iconSize;\n    if (m_isGtkButton) {\n        // See: https://github.com/Zren/material-decoration/issues/22\n        // kde-gtk-config has a kded5 module which renders the buttons to svgs for gtk.\n\n        // The svgs are 50x50, located at ~/.config/gtk-3.0/assets/\n        // They are usually scaled down to just 18x18 when drawn in gtk headerbars.\n        // The Gtk theme already has a fairly large amount of padding, as\n        // the Breeze theme doesn't currently follow fitt's law. So use less padding\n        // around the icon so that the icon is not a very tiny 8px.\n\n        // 15% top/bottom padding, 70% leftover for the icon.\n        // 24 = 3.5 topPadding + 17 icon + 3.5 bottomPadding\n        // 17/24 * 18 = 12.75\n        iconSize = qRound(iconScale * 17);\n    } else {\n        // 30% top/bottom padding, 40% leftover for the icon.\n        // 24 = 7 topPadding + 10 icon + 7 bottomPadding\n        iconSize = qRound(iconScale * 10);\n    }\n    QRectF iconRect = QRectF(0, 0, iconSize, iconSize);\n    iconRect.moveCenter(contentRect.center().toPoint());\n\n    const qreal gridUnit = iconRect.height()/10;\n\n    painter->save();\n\n    painter->setRenderHints(QPainter::Antialiasing, false);\n\n    // Opacity\n    painter->setOpacity(m_opacity);\n\n    // Background.\n    painter->setPen(Qt::NoPen);\n    painter->setBrush(backgroundColor());\n    painter->drawRect(buttonRect);\n\n    // Foreground.\n    setPenWidth(painter, gridUnit, 1);\n    painter->setBrush(Qt::NoBrush);\n\n\n    // Icon\n    switch (type()) {\n    case KDecoration2::DecorationButtonType::Menu:\n        AppIconButton::paintIcon(this, painter, iconRect, gridUnit);\n        break;\n\n    case KDecoration2::DecorationButtonType::ApplicationMenu:\n        ApplicationMenuButton::paintIcon(this, painter, iconRect, gridUnit);\n        break;\n\n    case KDecoration2::DecorationButtonType::OnAllDesktops:\n        OnAllDesktopsButton::paintIcon(this, painter, iconRect, gridUnit);\n        break;\n\n    case KDecoration2::DecorationButtonType::ContextHelp:\n        ContextHelpButton::paintIcon(this, painter, iconRect, gridUnit);\n        break;\n\n    case KDecoration2::DecorationButtonType::Shade:\n        ShadeButton::paintIcon(this, painter, iconRect, gridUnit);\n        break;\n\n    case KDecoration2::DecorationButtonType::KeepAbove:\n        KeepAboveButton::paintIcon(this, painter, iconRect, gridUnit);\n        break;\n\n    case KDecoration2::DecorationButtonType::KeepBelow:\n        KeepBelowButton::paintIcon(this, painter, iconRect, gridUnit);\n        break;\n\n    case KDecoration2::DecorationButtonType::Close:\n        CloseButton::paintIcon(this, painter, iconRect, gridUnit);\n        break;\n\n    case KDecoration2::DecorationButtonType::Maximize:\n        MaximizeButton::paintIcon(this, painter, iconRect, gridUnit);\n        break;\n\n    case KDecoration2::DecorationButtonType::Minimize:\n        MinimizeButton::paintIcon(this, painter, iconRect, gridUnit);\n        break;\n\n    default:\n        paintIcon(painter, iconRect, gridUnit);\n        break;\n    }\n\n    painter->restore();\n}\n\nvoid Button::paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit)\n{\n    Q_UNUSED(painter)\n    Q_UNUSED(iconRect)\n    Q_UNUSED(gridUnit)\n}\n\nvoid Button::updateSize(int contentWidth, int contentHeight)\n{\n    const QSize size(\n        m_padding->left() + contentWidth + m_padding->right(),\n        m_padding->top() + contentHeight + m_padding->bottom()\n    );\n    setGeometry(QRect(geometry().topLeft().toPoint(), size));\n}\n\nvoid Button::setHeight(int buttonHeight)\n{\n    // For simplicity, don't count the 1.33:1 scaling in the left/right padding.\n    // The left/right padding is mainly for the border offset alignment.\n    updateSize(qRound(buttonHeight * 1.33), buttonHeight);\n}\n\nqreal Button::iconLineWidth(const qreal gridUnit) const\n{\n    return PenWidth::Symbol * qMax(1.0, gridUnit);\n}\n\nvoid Button::setPenWidth(QPainter *painter, const qreal gridUnit, const qreal scale)\n{\n    QPen pen(foregroundColor());\n    pen.setCapStyle(Qt::RoundCap);\n    pen.setJoinStyle(Qt::MiterJoin);\n    pen.setWidthF(iconLineWidth(gridUnit) * scale);\n    painter->setPen(pen);\n}\n\nQColor Button::backgroundColor() const\n{\n    const auto *deco = qobject_cast<Decoration *>(decoration());\n    if (!deco) {\n        return {};\n    }\n\n    if (m_isGtkButton) {\n        // Breeze GTK has huge margins around the button. It looks better\n        // when we just change the fgColor on hover instead of the bgColor.\n        return Qt::transparent;\n    }\n\n    //--- CloseButton\n    if (type() == KDecoration2::DecorationButtonType::Close) {\n        auto *decoratedClient = deco->client().toStrongRef().data();\n        const QColor hoveredColor = decoratedClient->color(\n            KDecoration2::ColorGroup::Warning,\n            KDecoration2::ColorRole::Foreground\n        );\n        QColor normalColor = QColor(hoveredColor);\n        normalColor.setAlphaF(0);\n\n        if (isPressed()) {\n            const QColor pressedColor = decoratedClient->color(\n                KDecoration2::ColorGroup::Warning,\n                KDecoration2::ColorRole::Foreground\n            ).lighter();\n            return KColorUtils::mix(normalColor, pressedColor, m_transitionValue);\n        }\n\n        if (isHovered()) {\n            return KColorUtils::mix(normalColor, hoveredColor, m_transitionValue);\n        }\n    }\n\n    //--- Checked\n    if (isChecked() && type() != KDecoration2::DecorationButtonType::Maximize) {\n        const QColor normalColor = deco->titleBarForegroundColor();\n\n        if (isPressed()) {\n            const QColor pressedColor = KColorUtils::mix(\n                deco->titleBarBackgroundColor(),\n                deco->titleBarForegroundColor(),\n                0.7);\n            return KColorUtils::mix(normalColor, pressedColor, m_transitionValue);\n        }\n        if (isHovered()) {\n            const QColor hoveredColor = KColorUtils::mix(\n                deco->titleBarBackgroundColor(),\n                deco->titleBarForegroundColor(),\n                0.8);\n            return KColorUtils::mix(normalColor, hoveredColor, m_transitionValue);\n        }\n        return normalColor;\n    }\n\n    //--- Normal\n    const QColor hoveredColor = KColorUtils::mix(\n        deco->titleBarBackgroundColor(),\n        deco->titleBarForegroundColor(),\n        0.2);\n    QColor normalColor = QColor(hoveredColor);\n    normalColor.setAlphaF(0);\n\n    if (isPressed()) {\n        const QColor pressedColor = KColorUtils::mix(\n            deco->titleBarBackgroundColor(),\n            deco->titleBarForegroundColor(),\n            0.3);\n        return KColorUtils::mix(normalColor, pressedColor, m_transitionValue);\n    }\n    if (isHovered()) {\n        return KColorUtils::mix(normalColor, hoveredColor, m_transitionValue);\n    }\n    return normalColor;\n}\n\nQColor Button::foregroundColor() const\n{\n    const auto *deco = qobject_cast<Decoration *>(decoration());\n    if (!deco) {\n        return {};\n    }\n\n    //--- Checked\n    if (isChecked() && type() != KDecoration2::DecorationButtonType::Maximize) {\n        const QColor activeColor = KColorUtils::mix(\n            deco->titleBarBackgroundColor(),\n            deco->titleBarForegroundColor(),\n            0.2);\n\n        if (isPressed() || isHovered()) {\n            return KColorUtils::mix(\n                activeColor,\n                deco->titleBarBackgroundColor(),\n                m_transitionValue);\n        }\n        return activeColor;\n    }\n\n    //--- Normal\n    const QColor normalColor = KColorUtils::mix(\n        deco->titleBarBackgroundColor(),\n        deco->titleBarForegroundColor(),\n        0.8);\n\n    if (isPressed() || isHovered()) {\n        // Breeze GTK has huge margins around the button. It looks better\n        // when we just change the fgColor on hover instead of the bgColor.\n        QColor hoveredColor;\n        if (m_isGtkButton && type() == KDecoration2::DecorationButtonType::Close) {\n            auto *decoratedClient = deco->client().toStrongRef().data();\n            hoveredColor = decoratedClient->color(\n                KDecoration2::ColorGroup::Warning,\n                KDecoration2::ColorRole::Foreground\n            );\n        } else if (m_isGtkButton && type() == KDecoration2::DecorationButtonType::Maximize) {\n            const int grayValue = qGray(deco->titleBarBackgroundColor().rgb());\n            if (grayValue < 128) { // Dark Bg\n                hoveredColor = QColor(100, 196, 86); // from SierraBreeze\n            } else { // Light Bg\n                hoveredColor = QColor(40, 200, 64); // from SierraBreeze\n            }\n        } else if (m_isGtkButton && type() == KDecoration2::DecorationButtonType::Minimize) {\n            const int grayValue = qGray(deco->titleBarBackgroundColor().rgb());\n            if (grayValue < 128) {\n                hoveredColor = QColor(223, 192, 76); // from SierraBreeze\n            } else { // Light Bg\n                hoveredColor = QColor(255, 188, 48); // from SierraBreeze\n            }\n        } else {\n            hoveredColor = deco->titleBarForegroundColor();\n        }\n\n        return KColorUtils::mix(\n            normalColor,\n            hoveredColor,\n            m_transitionValue);\n    }\n\n    return normalColor;\n}\n\n\nQRectF Button::contentArea() const\n{\n    return geometry().adjusted(\n        m_padding->left(),\n        m_padding->top(),\n        -m_padding->right(),\n        -m_padding->bottom()\n    );\n}\n\nbool Button::animationEnabled() const\n{\n    return m_animationEnabled;\n}\n\nvoid Button::setAnimationEnabled(bool value)\n{\n    if (m_animationEnabled != value) {\n        m_animationEnabled = value;\n        emit animationEnabledChanged();\n    }\n}\n\nint Button::animationDuration() const\n{\n    return m_animation->duration();\n}\n\nvoid Button::setAnimationDuration(int value)\n{\n    if (m_animation->duration() != value) {\n        m_animation->setDuration(value);\n        emit animationDurationChanged();\n    }\n}\n\nqreal Button::opacity() const\n{\n    return m_opacity;\n}\n\nvoid Button::setOpacity(qreal value)\n{\n    if (m_opacity != value) {\n        m_opacity = value;\n        emit opacityChanged();\n    }\n}\n\nqreal Button::transitionValue() const\n{\n    return m_transitionValue;\n}\n\nvoid Button::setTransitionValue(qreal value)\n{\n    if (m_transitionValue != value) {\n        m_transitionValue = value;\n        emit transitionValueChanged(value);\n    }\n}\n\nQMargins* Button::padding()\n{\n    return m_padding;\n}\n\nvoid Button::setHorzPadding(int value)\n{\n    padding()->setLeft(value);\n    padding()->setRight(value);\n}\n\nvoid Button::setVertPadding(int value)\n{\n    padding()->setTop(value);\n    padding()->setBottom(value);\n}\n\nvoid Button::updateAnimationState(bool hovered)\n{\n    if (m_animationEnabled) {\n        QAbstractAnimation::Direction dir = hovered ? QAbstractAnimation::Forward : QAbstractAnimation::Backward;\n        if (m_animation->state() == QAbstractAnimation::Running && m_animation->direction() != dir) {\n            m_animation->stop();\n        }\n        m_animation->setDirection(dir);\n        if (m_animation->state() != QAbstractAnimation::Running) {\n            m_animation->start();\n        }\n    } else {\n        setTransitionValue(1);\n    }\n}\n\n\n} // namespace Material\n"
  },
  {
    "path": "src/Button.h",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// KDecoration\n#include <KDecoration2/Decoration>\n#include <KDecoration2/DecorationButton>\n\n// Qt\n#include <QMargins>\n#include <QRectF>\n#include <QVariantAnimation>\n\nnamespace Material\n{\n\nclass Decoration;\n\nclass Button : public KDecoration2::DecorationButton\n{\n    Q_OBJECT\n\npublic:\n    Button(KDecoration2::DecorationButtonType type, Decoration *decoration, QObject *parent = nullptr);\n    ~Button() override;\n\n    Q_PROPERTY(bool animationEnabled READ animationEnabled WRITE setAnimationEnabled NOTIFY animationEnabledChanged)\n    Q_PROPERTY(int animationDuration READ animationDuration WRITE setAnimationDuration NOTIFY animationDurationChanged)\n    Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged)\n    Q_PROPERTY(qreal transitionValue READ transitionValue WRITE setTransitionValue NOTIFY transitionValueChanged)\n    Q_PROPERTY(QMargins* padding READ padding NOTIFY paddingChanged)\n\n    // Passed to DecorationButtonGroup in Decoration\n    static KDecoration2::DecorationButton *create(KDecoration2::DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent = nullptr);\n\n    // This is called by:\n    // registerPlugin<Material::Button>(QStringLiteral(\"button\"))\n    // It is needed to create buttons for applet-window-buttons.\n    explicit Button(QObject *parent, const QVariantList &args);\n\n\n    void paint(QPainter *painter, const QRect &repaintRegion) override;\n    virtual void paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit);\n\n    virtual void updateSize(int contentWidth, int contentHeight);\n    virtual void setHeight(int buttonHeight);\n\n    virtual qreal iconLineWidth(const qreal gridUnit) const;\n    void setPenWidth(QPainter *painter, const qreal gridUnit, const qreal scale);\n\n    virtual QColor backgroundColor() const;\n    virtual QColor foregroundColor() const;\n\n    QRectF contentArea() const;\n\n    bool animationEnabled() const;\n    void setAnimationEnabled(bool value);\n\n    int animationDuration() const;\n    void setAnimationDuration(int duration);\n\n    qreal opacity() const;\n    void setOpacity(qreal value);\n\n    qreal transitionValue() const;\n    void setTransitionValue(qreal value);\n\n    QMargins* padding();\n    void setHorzPadding(int value);\n    void setVertPadding(int value);\n\nprivate Q_SLOTS:\n    void updateAnimationState(bool hovered);\n\nsignals:\n    void animationEnabledChanged();\n    void animationDurationChanged();\n    void opacityChanged();\n    void transitionValueChanged(qreal);\n    void paddingChanged();\n\nprivate:\n    bool m_animationEnabled;\n    QVariantAnimation *m_animation;\n    qreal m_opacity;\n    qreal m_transitionValue;\n    QMargins *m_padding;\n    bool m_isGtkButton;\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/CMakeLists.txt",
    "content": "find_package (KDecoration2 REQUIRED)\n\nfind_package (Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS\n    Core\n    Gui\n)\n\nfind_package (KF5 REQUIRED COMPONENTS\n    Config\n    ConfigWidgets\n    CoreAddons\n    GuiAddons\n    I18n\n    IconThemes\n    WindowSystem\n)\n\n\n# X11\nfind_package(X11 REQUIRED)\nset_package_properties(X11 PROPERTIES DESCRIPTION \"X11 libraries\"\n    URL \"http://www.x.org\"\n    TYPE REQUIRED\n    PURPOSE \"Required for building the X11 based workspace\"\n)\n\nfind_package(XCB MODULE REQUIRED COMPONENTS\n    XCB\n    RANDR\n)\nset_package_properties(XCB PROPERTIES TYPE REQUIRED)\n\nif (Qt6_FOUND)\n    # The QX11Info class has been removed.\n    # Clients that still rely on the functionality can include the private header <QtGui/private/qtx11extras_p.h> as a stopgap solution.\n    # To enable private headers use QT += gui-private with qmake, or add a project dependency to Qt::GuiPrivate with CMake.\n    # https://doc.qt.io/qt-6/extras-changes-qt6.html#changes-to-qt-x11-extras\n    find_package(Qt${QT_VERSION_MAJOR} ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS GuiPrivate)\nelseif(Qt5_FOUND)\n    find_package(Qt${QT_VERSION_MAJOR} ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS X11Extras)\nendif()\n\n\nif(X11_FOUND AND XCB_XCB_FOUND)\n    set(HAVE_X11 ON)\n    set(X11_LIBRARIES XCB::XCB)\nendif()\n\n\n# Wayland\nfind_package (KF5 COMPONENTS\n    Wayland\n)\n\nif(KF5Wayland_FOUND)\n    set(HAVE_Wayland ON)\n    set(Wayland_LIBRARIES KF5::WaylandClient)\nendif()\n\n\n# KDecoration2/Plasma Version\nif(${KDecoration2_VERSION} VERSION_GREATER_EQUAL \"5.25.0\")\n    set(HAVE_KDecoration2_5_25 ON)\nelse()\n    set(HAVE_KDecoration2_5_25 OFF)\nendif()\nmessage(STATUS \"HAVE_KDecoration2_5_25: ${HAVE_KDecoration2_5_25} (${KDecoration2_VERSION})\")\n\n# KF5 Version\nif(${KF5_VERSION} VERSION_GREATER_EQUAL \"5.101.0\")\n    set(HAVE_KF5_101 ON)\nelse()\n    set(HAVE_KF5_101 OFF)\nendif()\nmessage(STATUS \"HAVE_KF5_101: ${HAVE_KF5_101} (${KF5_VERSION})\")\n\n\nconfigure_file(BuildConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/BuildConfig.h)\n\nset (decoration_SRCS\n    AppMenuModel.cc\n    AppMenuButton.cc\n    AppMenuButtonGroup.cc\n    BoxShadowHelper.cc\n    Button.cc\n    Decoration.cc\n    MenuOverflowButton.cc\n    TextButton.cc\n    ConfigurationModule.cc\n    plugin.cc\n)\n\nkconfig_add_kcfg_files(decoration_SRCS\n    InternalSettings.kcfgc\n)\n\nadd_library (materialdecoration MODULE\n    ${decoration_SRCS}\n)\n\ntarget_link_libraries (materialdecoration\n    PUBLIC\n        dbusmenuqt\n        Qt${QT_VERSION_MAJOR}::Core\n        Qt${QT_VERSION_MAJOR}::Gui\n        # Qt${QT_VERSION_MAJOR}::X11Extras\n        KF5::ConfigCore\n        KF5::ConfigGui\n        KF5::ConfigWidgets\n        KF5::CoreAddons\n        KF5::I18n\n        KF5::GuiAddons\n        KF5::IconThemes\n        KF5::WindowSystem\n        ${X11_LIBRARIES}\n        ${Wayland_LIBRARIES}\n\n    PRIVATE\n        KDecoration2::KDecoration\n)\nif (Qt6_FOUND)\n    # The QX11Info class has been removed.\n    target_link_libraries (materialdecoration\n        PUBLIC\n            Qt${QT_VERSION_MAJOR}::GuiPrivate\n    )\nelseif(Qt5_FOUND)\n    target_link_libraries (materialdecoration\n        PUBLIC\n            Qt${QT_VERSION_MAJOR}::X11Extras\n    )\nendif()\n\ninstall (TARGETS materialdecoration\n         DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kdecoration2)\n"
  },
  {
    "path": "src/CloseButton.h",
    "content": "/*\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// own\n#include \"Button.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n\n// Qt\n#include <QPainter>\n\nnamespace Material\n{\n\nclass CloseButton\n{\n\npublic:\n    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {\n        QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::closeableChanged,\n                button, &Button::setVisible);\n\n        button->setVisible(decoratedClient->isCloseable());\n    }\n    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {\n        Q_UNUSED(button)\n        Q_UNUSED(gridUnit)\n\n        painter->setRenderHints(QPainter::Antialiasing, true);\n\n        button->setPenWidth(painter, gridUnit, 1.10);\n\n        painter->drawLine(iconRect.topLeft(), iconRect.bottomRight());\n        painter->drawLine(iconRect.topRight(), iconRect.bottomLeft());\n    }\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/ConfigurationModule.cc",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2012 Martin Gräßlin <mgraesslin@kde.org>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n// own\n#include \"ConfigurationModule.h\"\n#include \"Material.h\"\n#include \"InternalSettings.h\"\n\n// KF\n#include <KColorButton>\n#include <KConfigSkeleton>\n#include <KCoreConfigSkeleton>\n#include <KCModule>\n#include <KLocalizedString>\n#include <KSharedConfig>\n\n// KDecoration\n#include <KDecoration2/DecorationButton>\n\n// Qt\n#include <QDebug>\n\n// QWidget\n#include <QButtonGroup>\n#include <QCheckBox>\n#include <QComboBox>\n#include <QDoubleSpinBox>\n#include <QFormLayout>\n#include <QLabel>\n#include <QRadioButton>\n#include <QSpinBox>\n#include <QTabWidget>\n#include <QWidget>\n#include <QVBoxLayout>\n\nnamespace Material\n{\n\n\nConfigurationModule::ConfigurationModule(QWidget *parent, const QVariantList &args)\n    : KCModule(parent, args)\n    , m_titleAlignment(InternalSettings::AlignCenterFullWidth)\n    , m_buttonSize(InternalSettings::ButtonDefault)\n    , m_shadowSize(InternalSettings::ShadowVeryLarge)\n{\n    init();\n}\n\nvoid ConfigurationModule::init()\n{\n    KCoreConfigSkeleton *skel = new KCoreConfigSkeleton(KSharedConfig::openConfig(s_configFilename), this);\n    skel->setCurrentGroup(QStringLiteral(\"Windeco\"));\n\n    // See fr.po for the messages we can reuse from breeze:\n    // https://websvn.kde.org/*checkout*/trunk/l10n-kf5/fr/messages/breeze/breeze_kwin_deco.po\n\n    //--- Tabs\n    QTabWidget *tabWidget = new QTabWidget(this);\n    QVBoxLayout *mainLayout = new QVBoxLayout(this);\n    mainLayout->addWidget(tabWidget);\n    mainLayout->addStretch(1);\n    setLayout(mainLayout);\n\n    //--- General\n    QWidget *generalTab = new QWidget(tabWidget);\n    tabWidget->addTab(generalTab, i18nd(\"breeze_kwin_deco\", \"General\"));\n    QFormLayout *generalForm = new QFormLayout(generalTab);\n    generalTab->setLayout(generalForm);\n\n    QComboBox *titleAlignment = new QComboBox(generalTab);\n    titleAlignment->addItem(i18nd(\"breeze_kwin_deco\", \"Left\"));\n    titleAlignment->addItem(i18nd(\"breeze_kwin_deco\", \"Center\"));\n    titleAlignment->addItem(i18nd(\"breeze_kwin_deco\", \"Center (Full Width)\"));\n    titleAlignment->addItem(i18nd(\"breeze_kwin_deco\", \"Right\"));\n    titleAlignment->addItem(i18n(\"Hidden\"));\n    titleAlignment->setObjectName(QStringLiteral(\"kcfg_TitleAlignment\"));\n    generalForm->addRow(i18nd(\"breeze_kwin_deco\", \"Tit&le alignment:\"), titleAlignment);\n\n    QComboBox *buttonSizes = new QComboBox(generalTab);\n    buttonSizes->addItem(i18nd(\"breeze_kwin_deco\", \"Tiny\"));\n    buttonSizes->addItem(i18ndc(\"breeze_kwin_deco\", \"@item:inlistbox Button size:\", \"Small\"));\n    buttonSizes->addItem(i18ndc(\"breeze_kwin_deco\", \"@item:inlistbox Button size:\", \"Medium\"));\n    buttonSizes->addItem(i18ndc(\"breeze_kwin_deco\", \"@item:inlistbox Button size:\", \"Large\"));\n    buttonSizes->addItem(i18ndc(\"breeze_kwin_deco\", \"@item:inlistbox Button size:\", \"Very Large\"));\n    buttonSizes->setObjectName(QStringLiteral(\"kcfg_ButtonSize\"));\n    generalForm->addRow(i18nd(\"breeze_kwin_deco\", \"B&utton size:\"), buttonSizes);\n\n    QDoubleSpinBox *activeOpacity = new QDoubleSpinBox(generalTab);\n    activeOpacity->setMinimum(0.0);\n    activeOpacity->setMaximum(1.0);\n    activeOpacity->setSingleStep(0.05);\n    activeOpacity->setObjectName(QStringLiteral(\"kcfg_ActiveOpacity\"));\n    generalForm->addRow(i18n(\"Active Opacity:\"), activeOpacity);\n\n    QDoubleSpinBox *inactiveOpacity = new QDoubleSpinBox(generalTab);\n    inactiveOpacity->setMinimum(0.0);\n    inactiveOpacity->setMaximum(1.0);\n    inactiveOpacity->setSingleStep(0.05);\n    inactiveOpacity->setObjectName(QStringLiteral(\"kcfg_InactiveOpacity\"));\n    generalForm->addRow(i18n(\"Inactive Opacity:\"), inactiveOpacity);\n\n\n    //--- Menu\n    QWidget *menuTab = new QWidget(tabWidget);\n    tabWidget->addTab(menuTab, i18n(\"Menu\"));\n    QFormLayout *menuForm = new QFormLayout(menuTab);\n    menuTab->setLayout(menuForm);\n\n    QLabel *menuLabel = new QLabel(menuTab);\n    menuLabel->setText(i18n(\"To enable the Locally Integrated Menus in the titlebar:\\nSystem Settings > Window Decorations > Titlebar Buttons Tab\\nDrag the 'Application Menu' button to bar.\"));\n    menuForm->addRow(QStringLiteral(\"\"), menuLabel);\n\n    QRadioButton *menuAlwaysShow = new QRadioButton(menuTab);\n    menuAlwaysShow->setText(i18n(\"Always Show Menu\"));\n    menuAlwaysShow->setObjectName(QStringLiteral(\"kcfg_MenuAlwaysShow\"));\n    menuForm->addRow(QStringLiteral(\"\"), menuAlwaysShow);\n\n    // Since there's no easy way to bind this to !MenuAlwaysShow, we\n    // workaround this by marking the button as checked on init.\n    // When the config is loaded:\n    // * If menuAlwaysShow is toggled true, this will be toggled false.\n    // * If menuAlwaysShow is left false, then this remains true.\n    QRadioButton *menuRevealOnHover = new QRadioButton(menuTab);\n    menuRevealOnHover->setText(i18n(\"Reveal Menu on Hover\"));\n    menuRevealOnHover->setChecked(true);\n    menuForm->addRow(QStringLiteral(\"\"), menuRevealOnHover);\n\n    QButtonGroup *menuAlwaysShowGroup = new QButtonGroup(menuTab);\n    menuAlwaysShowGroup->addButton(menuAlwaysShow);\n    menuAlwaysShowGroup->addButton(menuRevealOnHover);\n\n    QSpinBox *menuButtonHorzPadding = new QSpinBox(menuTab);\n    menuButtonHorzPadding->setMinimum(0);\n    menuButtonHorzPadding->setMaximum(INT_MAX);\n    menuButtonHorzPadding->setObjectName(QStringLiteral(\"kcfg_MenuButtonHorzPadding\"));\n    menuForm->addRow(i18n(\"Padding:\"), menuButtonHorzPadding);\n\n\n    //--- Animations\n    QWidget *animationsTab = new QWidget(tabWidget);\n    tabWidget->addTab(animationsTab, i18nd(\"breeze_kwin_deco\", \"Animations\"));\n    QFormLayout *animationsForm = new QFormLayout(animationsTab);\n    animationsTab->setLayout(animationsForm);\n\n    QCheckBox *animationsEnabled = new QCheckBox(animationsTab);\n    animationsEnabled->setText(i18nd(\"breeze_kwin_deco\", \"Enable animations\"));\n    animationsEnabled->setObjectName(QStringLiteral(\"kcfg_AnimationsEnabled\"));\n    animationsForm->addRow(QStringLiteral(\"\"), animationsEnabled);\n\n    QSpinBox *animationsDuration = new QSpinBox(animationsTab);\n    animationsDuration->setMinimum(0);\n    animationsDuration->setMaximum(INT_MAX);\n    animationsDuration->setSuffix(i18nd(\"breeze_kwin_deco\", \" ms\"));\n    animationsDuration->setObjectName(QStringLiteral(\"kcfg_AnimationsDuration\"));\n    animationsForm->addRow(i18nd(\"breeze_kwin_deco\", \"Animations:\"), animationsDuration);\n\n\n    //--- Shadows\n    QWidget *shadowTab = new QWidget(tabWidget);\n    tabWidget->addTab(shadowTab, i18nd(\"breeze_kwin_deco\", \"Shadows\"));\n    QFormLayout *shadowForm = new QFormLayout(shadowTab);\n    shadowTab->setLayout(shadowForm);\n\n    QComboBox *shadowSizes = new QComboBox(shadowTab);\n    shadowSizes->addItem(i18ndc(\"breeze_kwin_deco\", \"@item:inlistbox Button size:\", \"None\"));\n    shadowSizes->addItem(i18ndc(\"breeze_kwin_deco\", \"@item:inlistbox Button size:\", \"Small\"));\n    shadowSizes->addItem(i18ndc(\"breeze_kwin_deco\", \"@item:inlistbox Button size:\", \"Medium\"));\n    shadowSizes->addItem(i18ndc(\"breeze_kwin_deco\", \"@item:inlistbox Button size:\", \"Large\"));\n    shadowSizes->addItem(i18ndc(\"breeze_kwin_deco\", \"@item:inlistbox Button size:\", \"Very Large\"));\n    shadowSizes->setObjectName(QStringLiteral(\"kcfg_ShadowSize\"));\n    shadowForm->addRow(i18nd(\"breeze_kwin_deco\", \"Si&ze:\"), shadowSizes);\n\n    QSpinBox *shadowStrength = new QSpinBox(shadowTab);\n    shadowStrength->setMinimum(25);\n    shadowStrength->setMaximum(255);\n    // shadowStrength->setSuffix(i18nd(\"breeze_kwin_deco\", \"%\"));\n    shadowStrength->setObjectName(QStringLiteral(\"kcfg_ShadowStrength\"));\n    shadowForm->addRow(i18ndc(\"breeze_kwin_deco\", \"strength of the shadow (from transparent to opaque)\", \"S&trength:\"), shadowStrength);\n\n    KColorButton *shadowColor = new KColorButton(shadowTab);\n    shadowColor->setObjectName(QStringLiteral(\"kcfg_ShadowColor\"));\n    shadowForm->addRow(i18nd(\"breeze_kwin_deco\", \"Color:\"), shadowColor);\n\n    //--- Config Bindings\n    skel->addItemInt(\n        QStringLiteral(\"TitleAlignment\"),\n        m_titleAlignment,\n        InternalSettings::AlignCenterFullWidth,\n        QStringLiteral(\"TitleAlignment\")\n    );\n    skel->addItemInt(\n        QStringLiteral(\"ButtonSize\"),\n        m_buttonSize,\n        InternalSettings::ButtonDefault,\n        QStringLiteral(\"ButtonSize\")\n    );\n    skel->addItemDouble(\n        QStringLiteral(\"ActiveOpacity\"),\n        m_activeOpacity,\n        0.75,\n        QStringLiteral(\"ActiveOpacity\")\n    );\n    skel->addItemDouble(\n        QStringLiteral(\"InactiveOpacity\"),\n        m_inactiveOpacity,\n        0.85,\n        QStringLiteral(\"InactiveOpacity\")\n    );\n    skel->addItemBool(\n        QStringLiteral(\"MenuAlwaysShow\"),\n        m_menuAlwaysShow,\n        true,\n        QStringLiteral(\"MenuAlwaysShow\")\n    );\n    skel->addItemInt(\n        QStringLiteral(\"MenuButtonHorzPadding\"),\n        m_menuButtonHorzPadding,\n        1,\n        QStringLiteral(\"MenuButtonHorzPadding\")\n    );\n    skel->addItemBool(\n        QStringLiteral(\"AnimationsEnabled\"),\n        m_animationsEnabled,\n        true,\n        QStringLiteral(\"AnimationsEnabled\")\n    );\n    skel->addItemInt(\n        QStringLiteral(\"AnimationsDuration\"),\n        m_animationsDuration,\n        150,\n        QStringLiteral(\"AnimationsDuration\")\n    );\n    skel->addItemInt(\n        QStringLiteral(\"ShadowSize\"),\n        m_shadowSize,\n        InternalSettings::ShadowVeryLarge,\n        QStringLiteral(\"ShadowSize\")\n    );\n    skel->addItemInt(\n        QStringLiteral(\"ShadowStrength\"),\n        m_shadowStrength,\n        255,\n        QStringLiteral(\"ShadowStrength\")\n    );\n    skel->addItem(new KConfigSkeleton::ItemColor(\n        skel->currentGroup(),\n        QStringLiteral(\"ShadowColor\"),\n        m_shadowColor,\n        QColor(33, 33, 33)\n    ), QStringLiteral(\"ShadowColor\"));\n\n    //---\n    addConfig(skel, this);\n}\n\n} // namespace Material\n"
  },
  {
    "path": "src/ConfigurationModule.h",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2012 Martin Gräßlin <mgraesslin@kde.org>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n// KF\n#include <KCModule>\n\n// Qt\n#include <QColor>\n#include <QWidget>\n\nnamespace Material\n{\n\nclass ConfigurationModule : public KCModule\n{\n    Q_OBJECT\npublic:\n    ConfigurationModule(QWidget *parent, const QVariantList &args);\n\nprivate:\n    void init();\n\n    int m_titleAlignment;\n    int m_buttonSize;\n    double m_activeOpacity;\n    double m_inactiveOpacity;\n    bool m_menuAlwaysShow;\n    int m_menuButtonHorzPadding;\n    bool m_animationsEnabled;\n    int m_animationsDuration;\n    int m_shadowSize;\n    int m_shadowStrength;\n    QColor m_shadowColor;\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/ContextHelpButton.h",
    "content": "/*\n * Copyright (C) 2021 Chris Holland <zrenfire@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// own\n#include \"Button.h\"\n#include \"Material.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n\n// Qt\n#include <QPainter>\n#include <QPainterPath>\n#include <QPointF>\n#include <QRectF>\n#include <QSizeF>\n\nnamespace Material\n{\n\nclass ContextHelpButton\n{\n\npublic:\n    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {\n        QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::providesContextHelpChanged,\n                button, &Button::setVisible);\n\n        button->setVisible(decoratedClient->providesContextHelp());\n    }\n    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {\n        button->setPenWidth(painter, gridUnit, 1.25);\n\n        painter->setRenderHints(QPainter::Antialiasing, true);\n        painter->translate( iconRect.topLeft() );\n\n        const QRectF topCurveRect = QRectF(\n            QPointF( 1.5, 0.5 ) * gridUnit,\n            QSizeF( 8, 6 ) * gridUnit\n        );\n        QPainterPath path;\n        path.moveTo( topCurveRect.center() - QPointF(topCurveRect.width()/2, 0) );\n        path.arcTo(\n            topCurveRect,\n            180,\n            -180\n        );\n        path.cubicTo(\n            QPointF( 7.8125, 5.9375 ) * gridUnit,\n            QPointF( 5.625, 4.6875 ) * gridUnit,\n            QPointF( 5, 8 ) * gridUnit\n        );\n        painter->drawPath(path);\n\n        // Dot\n        painter->drawRect( QRectF(\n            QPointF( 5, 10 ) * gridUnit,\n            QSizeF( 0.5, 0.5 ) * gridUnit\n        ));\n    }\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/Decoration.cc",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n * Copyright (C) 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>\n * Copyright (C) 2014 Martin Gräßlin <mgraesslin@kde.org>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n// own\n#include \"Decoration.h\"\n#include \"Material.h\"\n#include \"BuildConfig.h\"\n#include \"AppMenuButtonGroup.h\"\n#include \"BoxShadowHelper.h\"\n#include \"Button.h\"\n#include \"InternalSettings.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n#include <KDecoration2/DecorationButton>\n#include <KDecoration2/DecorationButtonGroup>\n#include <KDecoration2/DecorationSettings>\n#include <KDecoration2/DecorationShadow>\n\n// KF\n#include <KWindowSystem>\n\n// Qt\n#include <QApplication>\n#include <QDebug>\n#include <QHoverEvent>\n#include <QMouseEvent>\n#include <QPainter>\n#include <QRegion>\n#include <QSharedPointer>\n#include <QWheelEvent>\n\n// X11\n#if HAVE_X11\n#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)\n#include <private/qtx11extras_p.h>\n#else\n#include <QX11Info>\n#endif\n#include <xcb/xcb.h>\n#endif\n\nnamespace Material\n{\n\nnamespace\n{\n\nstruct ShadowParams\n{\n    ShadowParams() = default;\n\n    ShadowParams(const QPoint &offset, int radius, qreal opacity)\n        : offset(offset)\n        , radius(radius)\n        , opacity(opacity) {}\n\n    QPoint offset;\n    int radius = 0;\n    qreal opacity = 0;\n};\n\nstruct CompositeShadowParams\n{\n    CompositeShadowParams() = default;\n\n    CompositeShadowParams(\n            const QPoint &offset,\n            const ShadowParams &shadow1,\n            const ShadowParams &shadow2)\n        : offset(offset)\n        , shadow1(shadow1)\n        , shadow2(shadow2) {}\n\n    bool isNone() const {\n        return qMax(shadow1.radius, shadow2.radius) == 0;\n    }\n\n    QPoint offset;\n    ShadowParams shadow1;\n    ShadowParams shadow2;\n};\n\n// const CompositeShadowParams s_shadowParams = CompositeShadowParams(\n//     QPoint(0, 18),\n//     ShadowParams(QPoint(0, 0), 64, 0.8),\n//     ShadowParams(QPoint(0, -10), 24, 0.1)\n// );\nconst CompositeShadowParams s_shadowParams[] = {\n    // None\n    CompositeShadowParams(),\n    // Small\n    CompositeShadowParams(\n        QPoint(0, 4),\n        ShadowParams(QPoint(0, 0), 16, 1),\n        ShadowParams(QPoint(0, -2), 8, 0.4)),\n    // Medium\n    CompositeShadowParams(\n        QPoint(0, 8),\n        ShadowParams(QPoint(0, 0), 32, 0.9),\n        ShadowParams(QPoint(0, -4), 16, 0.3)),\n    // Large\n    CompositeShadowParams(\n        QPoint(0, 12),\n        ShadowParams(QPoint(0, 0), 48, 0.8),\n        ShadowParams(QPoint(0, -6), 24, 0.2)),\n    // Very large\n    CompositeShadowParams(\n        QPoint(0, 16),\n        ShadowParams(QPoint(0, 0), 64, 0.7),\n        ShadowParams(QPoint(0, -8), 32, 0.1)),\n};\n\ninline CompositeShadowParams lookupShadowParams(int size)\n{\n    switch (size) {\n    case InternalSettings::ShadowNone:\n        return s_shadowParams[0];\n    case InternalSettings::ShadowSmall:\n        return s_shadowParams[1];\n    case InternalSettings::ShadowMedium:\n        return s_shadowParams[2];\n    default:\n    case InternalSettings::ShadowLarge:\n        return s_shadowParams[3];\n    case InternalSettings::ShadowVeryLarge:\n        return s_shadowParams[4];\n    }\n}\n\n} // anonymous namespace\n\nstatic int s_decoCount = 0;\nstatic int s_shadowSizePreset = InternalSettings::ShadowVeryLarge;\nstatic int s_shadowStrength = 255;\nstatic QColor s_shadowColor = QColor(33, 33, 33);\nstatic QSharedPointer<KDecoration2::DecorationShadow> s_cachedShadow;\n\nDecoration::Decoration(QObject *parent, const QVariantList &args)\n    : KDecoration2::Decoration(parent, args)\n    , m_internalSettings(nullptr)\n{\n    ++s_decoCount;\n}\n\nDecoration::~Decoration()\n{\n    if (--s_decoCount == 0) {\n        s_cachedShadow.clear();\n    }\n}\n\nQRect Decoration::titleBarRect() const\n{\n    return QRect(0, 0, size().width(), titleBarHeight());\n}\n\nQRect Decoration::centerRect() const\n{\n    const bool leftButtonsVisible = !m_leftButtons->buttons().isEmpty();\n    const int leftOffset = m_leftButtons->geometry().right()\n        + (leftButtonsVisible ? settings()->smallSpacing() : 0);\n\n    const bool rightButtonsVisible = !m_rightButtons->buttons().isEmpty();\n    const int rightOffset = m_rightButtons->geometry().width()\n        + (rightButtonsVisible ? settings()->smallSpacing() : 0);\n\n    return titleBarRect().adjusted(\n        leftOffset,\n        0,\n        -rightOffset,\n        0\n    );\n}\n\nvoid Decoration::paint(QPainter *painter, const QRect &repaintRegion)\n{\n    auto *decoratedClient = client().toStrongRef().data();\n\n    if (!decoratedClient->isShaded()) {\n        paintFrameBackground(painter, repaintRegion);\n    }\n\n    paintTitleBarBackground(painter, repaintRegion);\n    paintButtons(painter, repaintRegion);\n    paintCaption(painter, repaintRegion);\n\n    // Don't paint outline for NoBorder, NoSideBorder, or Tiny borders.\n    if (settings()->borderSize() >= KDecoration2::BorderSize::Normal) {\n        paintOutline(painter, repaintRegion);\n    }\n    updateBlur();\n}\n\nvoid Decoration::init()\n{\n    m_internalSettings = QSharedPointer<InternalSettings>(new InternalSettings());\n\n    auto *decoratedClient = client().toStrongRef().data();\n\n    auto repaintTitleBar = [this] {\n        update(titleBar());\n    };\n\n    m_leftButtons = new KDecoration2::DecorationButtonGroup(\n        KDecoration2::DecorationButtonGroup::Position::Left,\n        this,\n        &Button::create);\n\n    m_rightButtons = new KDecoration2::DecorationButtonGroup(\n        KDecoration2::DecorationButtonGroup::Position::Right,\n        this,\n        &Button::create);\n\n    m_menuButtons = new AppMenuButtonGroup(this);\n    connect(m_menuButtons, &AppMenuButtonGroup::menuUpdated,\n            this, &Decoration::updateButtonsGeometry);\n    connect(m_menuButtons, &AppMenuButtonGroup::opacityChanged,\n            this, repaintTitleBar);\n    connect(m_menuButtons, &AppMenuButtonGroup::alwaysShowChanged,\n            this, repaintTitleBar);\n    m_menuButtons->updateAppMenuModel();\n\n\n    connect(decoratedClient, &KDecoration2::DecoratedClient::widthChanged,\n            this, &Decoration::updateTitleBar);\n    connect(decoratedClient, &KDecoration2::DecoratedClient::widthChanged,\n            this, &Decoration::updateButtonsGeometry);\n    connect(decoratedClient, &KDecoration2::DecoratedClient::maximizedChanged,\n            this, &Decoration::updateButtonsGeometry);\n\n    connect(decoratedClient, &KDecoration2::DecoratedClient::adjacentScreenEdgesChanged,\n            this, &Decoration::updateBorders);\n    connect(decoratedClient, &KDecoration2::DecoratedClient::maximizedHorizontallyChanged,\n            this, &Decoration::updateBorders);\n    connect(decoratedClient, &KDecoration2::DecoratedClient::maximizedVerticallyChanged,\n            this, &Decoration::updateBorders);\n    connect(decoratedClient, &KDecoration2::DecoratedClient::shadedChanged,\n            this, &Decoration::updateBorders);\n\n    connect(decoratedClient, &KDecoration2::DecoratedClient::captionChanged,\n            this, repaintTitleBar);\n    connect(decoratedClient, &KDecoration2::DecoratedClient::activeChanged,\n            this, repaintTitleBar);\n\n    updateBorders();\n    updateResizeBorders();\n    updateTitleBar();\n    updateButtonsGeometry();\n\n    connect(this, &KDecoration2::Decoration::sectionUnderMouseChanged,\n            this, &Decoration::onSectionUnderMouseChanged);\n    updateTitleBarHoverState();\n\n    // For some reason, the shadow should be installed the last. Otherwise,\n    // the Window Decorations KCM crashes.\n    updateShadow();\n\n    connect(settings().data(), &KDecoration2::DecorationSettings::reconfigured,\n        this, &Decoration::reconfigure);\n    connect(m_internalSettings.data(), &InternalSettings::configChanged,\n        this, &Decoration::reconfigure);\n\n    // Window Decoration KCM\n    // The reconfigure signal will update active windows, but we need to hook\n    // individual signals for the preview in the KCM.\n    connect(settings().data(), &KDecoration2::DecorationSettings::borderSizeChanged,\n        this, &Decoration::updateBorders);\n    connect(settings().data(), &KDecoration2::DecorationSettings::fontChanged,\n        this, &Decoration::updateBorders);\n    connect(settings().data(), &KDecoration2::DecorationSettings::spacingChanged,\n        this, &Decoration::updateBorders);\n}\n\nvoid Decoration::reconfigure()\n{\n    m_internalSettings->load();\n\n    updateBorders();\n    updateTitleBar();\n    m_menuButtons->setAlwaysShow(m_internalSettings->menuAlwaysShow());\n    updateButtonsGeometry();\n    updateButtonAnimation();\n    updateShadow();\n    update();\n}\n\nvoid Decoration::mousePressEvent(QMouseEvent *event)\n{\n    KDecoration2::Decoration::mousePressEvent(event);\n    // qCDebug(category) << \"Decoration::mousePressEvent\" << event;\n\n    if (m_menuButtons->geometry().contains(event->pos())) {\n        if (event->button() == Qt::LeftButton) {\n            initDragMove(event->pos());\n            event->setAccepted(false);\n\n        // If AppMenuButton's do not handle the button\n        } else if (event->button() == Qt::MiddleButton || event->button() == Qt::RightButton) {\n            // Don't accept the event. KDecoration2 will\n            // accept the event even if it doesn't pass\n            // button->acceptableButtons()->testFlag(button)\n            event->setAccepted(false);\n        }\n    }\n}\n\nvoid Decoration::hoverEnterEvent(QHoverEvent *event)\n{\n    KDecoration2::Decoration::hoverEnterEvent(event);\n    qCDebug(category) << \"Decoration::hoverEnterEvent\" << event;\n    updateBlur();\n    // m_menuButtons->setHovered(true);\n}\n\nvoid Decoration::hoverMoveEvent(QHoverEvent *event)\n{\n    KDecoration2::Decoration::hoverMoveEvent(event);\n    // qCDebug(category) << \"Decoration::hoverMoveEvent\" << event;\n\n    const bool dragStarted = dragMoveTick(event->pos());\n    // qCDebug(category) << \"    \" << \"dragStarted\" << dragStarted;\n    if (dragStarted) {\n        m_menuButtons->unPressAllButtons();\n    }\n\n    // const bool wasHovered = m_menuButtons->hovered();\n    // const bool contains = m_menuButtons->geometry().contains(event->posF());\n    // if (!wasHovered && contains) {\n    //     // HoverEnter\n    //     m_menuButtons->setHovered(true);\n    // } else if (wasHovered && !contains) {\n    //     // HoverLeave\n    //     m_menuButtons->setHovered(false);\n    // } else if (wasHovered && contains) {\n    //     // HoverMove\n    // }\n    updateBlur();\n}\n\nvoid Decoration::mouseReleaseEvent(QMouseEvent *event)\n{\n    KDecoration2::Decoration::mouseReleaseEvent(event);\n    // qCDebug(category) << \"Decoration::mouseReleaseEvent\" << event;\n\n    resetDragMove();\n    updateBlur();\n}\n\nvoid Decoration::hoverLeaveEvent(QHoverEvent *event)\n{\n    KDecoration2::Decoration::hoverLeaveEvent(event);\n    qCDebug(category) << \"Decoration::hoverLeaveEvent\" << event;\n\n    resetDragMove();\n    updateBlur();\n    // m_menuButtons->setHovered(false);\n}\n\nvoid Decoration::wheelEvent(QWheelEvent *event)\n{\n    #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)\n    const QPointF pos = event->position();\n    #else\n    const QPointF pos = event->posF();\n    #endif\n\n    if (m_menuButtons->geometry().contains(pos)) {\n        // Skip\n    } else {\n        KDecoration2::Decoration::wheelEvent(event);\n    }\n}\n\nvoid Decoration::onSectionUnderMouseChanged(const Qt::WindowFrameSection value)\n{\n    Q_UNUSED(value);\n    // qCDebug(category) << \"onSectionUnderMouseChanged\" << value;\n    updateTitleBarHoverState();\n}\n\nvoid Decoration::updateBlur()\n{\n#if HAVE_KDecoration2_5_25\n    setBlurRegion(QRegion(0, 0, size().width(), size().height()));\n#endif\n}\n\nvoid Decoration::updateBorders()\n{\n    const int sideSize = sideBorderSize();\n    QMargins borders;\n    borders.setTop(titleBarHeight());\n    borders.setLeft(leftBorderVisible() ? sideSize : 0);\n    borders.setRight(rightBorderVisible() ? sideSize : 0);\n    borders.setBottom(bottomBorderVisible() ? bottomBorderSize() : 0);\n    setBorders(borders);\n}\n\nvoid Decoration::updateResizeBorders()\n{\n    QMargins borders;\n\n    const int extender = settings()->largeSpacing();\n    borders.setLeft(extender);\n    borders.setTop(extender);\n    borders.setRight(extender);\n    borders.setBottom(extender);\n\n    setResizeOnlyBorders(borders);\n}\n\nvoid Decoration::updateTitleBar()\n{\n    setTitleBar(titleBarRect());\n}\n\nvoid Decoration::updateTitleBarHoverState()\n{\n    const bool wasHovered = m_menuButtons->hovered();\n    const bool isHovered = titleBarIsHovered();\n    if (!wasHovered && isHovered) {\n        // HoverEnter\n        m_menuButtons->setHovered(true);\n    } else if (wasHovered && !isHovered) {\n        // HoverLeave\n        m_menuButtons->setHovered(false);\n    } else if (wasHovered && isHovered) {\n        // HoverMove\n    }\n}\n\nvoid Decoration::setButtonGroupHeight(KDecoration2::DecorationButtonGroup *buttonGroup, int buttonHeight)\n{\n    // int vertPadding = buttonPadding();\n    for (int i = 0; i < buttonGroup->buttons().length(); i++) {\n        KDecoration2::DecorationButton* decoButton = buttonGroup->buttons().value(i);\n        auto *button = qobject_cast<Button *>(decoButton);\n        if (button) {\n            button->setHeight(buttonHeight);\n            // button->setVertPadding(vertPadding);\n        }\n    }\n}\n\nvoid Decoration::setButtonGroupHorzPadding(KDecoration2::DecorationButtonGroup *buttonGroup, int value)\n{\n    for (int i = 0; i < buttonGroup->buttons().length(); i++) {\n        KDecoration2::DecorationButton* decoButton = buttonGroup->buttons().value(i);\n        auto *button = qobject_cast<Button *>(decoButton);\n        if (button) {\n            button->setHorzPadding(value);\n        }\n    }\n}\n\nvoid Decoration::setButtonGroupVertPadding(KDecoration2::DecorationButtonGroup *buttonGroup, int value)\n{\n    for (int i = 0; i < buttonGroup->buttons().length(); i++) {\n        KDecoration2::DecorationButton* decoButton = buttonGroup->buttons().value(i);\n        auto *button = qobject_cast<Button *>(decoButton);\n        if (button) {\n            button->setVertPadding(value);\n        }\n    }\n}\n\nvoid Decoration::updateButtonHeight()\n{\n    const int buttonHeight = titleBarHeight();\n    setButtonGroupHeight(m_leftButtons, buttonHeight);\n    setButtonGroupHeight(m_rightButtons, buttonHeight);\n    setButtonGroupHeight(m_menuButtons, buttonHeight);\n}\n\nvoid Decoration::updateButtonsGeometry()\n{\n    const int sideSize = sideBorderSize();\n    const int leftOffset = leftBorderVisible() ? sideSize : 0;\n    const int rightOffset = rightBorderVisible() ? sideSize : 0;\n\n    updateButtonHeight();\n\n    // Left\n    m_leftButtons->setPos(QPointF(leftOffset, 0));\n    m_leftButtons->setSpacing(0);\n    // if (!m_leftButtons->buttons().isEmpty()) {\n    //     auto *firstButon = qobject_cast<Button *>(m_leftButtons->buttons().front());\n    //     firstButon->padding()->setLeft(leftOffset);\n    // }\n\n    // Right\n    m_rightButtons->setPos(QPointF(\n        size().width() - rightOffset - m_rightButtons->geometry().width(),\n        0\n    ));\n    m_rightButtons->setSpacing(0);\n    // if (!m_rightButtons->buttons().isEmpty()) {\n    //     auto *lastButton = qobject_cast<Button *>(m_rightButtons->buttons().last());\n    //     lastButton->padding()->setRight(rightOffset);\n    // }\n\n    // Menu\n    if (!m_menuButtons->buttons().isEmpty()) {\n        const int captionOffset = captionMinWidth() + settings()->smallSpacing();\n        const QRect availableRect = centerRect().adjusted(\n            0,\n            0,\n            -captionOffset,\n            0\n        );\n        setButtonGroupHorzPadding(m_menuButtons, m_internalSettings->menuButtonHorzPadding());\n        m_menuButtons->setPos(availableRect.topLeft());\n        m_menuButtons->setSpacing(0);\n        m_menuButtons->updateOverflow(availableRect);\n    }\n\n    update();\n}\n\nvoid Decoration::setButtonGroupAnimation(KDecoration2::DecorationButtonGroup *buttonGroup, bool enabled, int duration)\n{\n    for (int i = 0; i < buttonGroup->buttons().length(); i++) {\n        auto *button = qobject_cast<Button *>(buttonGroup->buttons().value(i));\n        button->setAnimationEnabled(enabled);\n        button->setAnimationDuration(duration);\n    }\n}\n\nvoid Decoration::updateButtonAnimation()\n{\n    const bool enabled = animationsEnabled();\n    const int duration = animationsDuration();\n    setButtonGroupAnimation(m_leftButtons, enabled, duration);\n    setButtonGroupAnimation(m_rightButtons, enabled, duration);\n    setButtonGroupAnimation(m_menuButtons, enabled, duration);\n\n    // Hover Animation\n    m_menuButtons->setAnimationEnabled(enabled);\n    m_menuButtons->setAnimationDuration(duration);\n}\n\nvoid Decoration::updateShadow()\n{\n    const QColor shadowColor = m_internalSettings->shadowColor();\n    const int shadowStrengthInt = m_internalSettings->shadowStrength();\n    const int shadowSizePreset = m_internalSettings->shadowSize();\n\n    if (!s_cachedShadow.isNull()\n        && s_shadowColor == shadowColor\n        && s_shadowSizePreset == shadowSizePreset\n        && s_shadowStrength == shadowStrengthInt\n    ) {\n        setShadow(s_cachedShadow);\n        return;\n    }\n\n    s_shadowColor = shadowColor;\n    s_shadowStrength = shadowStrengthInt;\n    s_shadowSizePreset = shadowSizePreset;\n\n    auto withOpacity = [] (const QColor &color, qreal opacity) -> QColor {\n        QColor c(color);\n        c.setAlphaF(opacity);\n        return c;\n    };\n\n    const qreal shadowStrength = static_cast<qreal>(shadowStrengthInt) / 255.0;\n    const CompositeShadowParams params = lookupShadowParams(shadowSizePreset);\n\n    if (params.isNone()) { // InternalSettings::ShadowNone\n        s_cachedShadow.clear();\n        setShadow(s_cachedShadow);\n        return;\n    }\n\n    // In order to properly render a box shadow with a given radius `shadowSize`,\n    // the box size should be at least `2 * QSize(shadowSize, shadowSize)`.\n    const int shadowSize = qMax(params.shadow1.radius, params.shadow2.radius);\n    const QSize boxSize = QSize(1, 1) + QSize(shadowSize*2, shadowSize*2);\n    const QRect box(QPoint(shadowSize, shadowSize), boxSize);\n    const QRect rect = box.adjusted(-shadowSize, -shadowSize, shadowSize, shadowSize);\n\n    QImage shadowTexture(rect.size(), QImage::Format_ARGB32_Premultiplied);\n    shadowTexture.fill(Qt::transparent);\n\n    QPainter painter(&shadowTexture);\n    painter.setRenderHint(QPainter::Antialiasing);\n\n    // Draw the \"shape\" shadow.\n    BoxShadowHelper::boxShadow(\n        &painter,\n        box,\n        params.shadow1.offset,\n        params.shadow1.radius,\n        withOpacity(shadowColor, params.shadow1.opacity * shadowStrength));\n\n    // Draw the \"contrast\" shadow.\n    BoxShadowHelper::boxShadow(\n        &painter,\n        box,\n        params.shadow2.offset,\n        params.shadow2.radius,\n        withOpacity(shadowColor, params.shadow2.opacity * shadowStrength));\n\n    // Mask out inner rect.\n    const QMargins padding = QMargins(\n        shadowSize - params.offset.x(),\n        shadowSize - params.offset.y(),\n        shadowSize + params.offset.x(),\n        shadowSize + params.offset.y());\n    const QRect innerRect = rect - padding;\n\n    // Mask out window+titlebar from shadow\n    painter.setPen(Qt::NoPen);\n    painter.setBrush(Qt::black);\n    painter.setCompositionMode(QPainter::CompositionMode_DestinationOut);\n    painter.drawRect(innerRect);\n\n    painter.end();\n\n    s_cachedShadow = QSharedPointer<KDecoration2::DecorationShadow>::create();\n    s_cachedShadow->setPadding(padding);\n    s_cachedShadow->setInnerShadowRect(QRect(shadowTexture.rect().center(), QSize(1, 1)));\n    s_cachedShadow->setShadow(shadowTexture);\n\n    setShadow(s_cachedShadow);\n}\n\nbool Decoration::menuAlwaysShow() const\n{\n    return m_internalSettings->menuAlwaysShow();\n}\n\nbool Decoration::animationsEnabled() const\n{\n    return m_internalSettings->animationsEnabled();\n}\n\nint Decoration::animationsDuration() const\n{\n    return m_internalSettings->animationsDuration();\n}\n\nint Decoration::buttonPadding() const\n{\n    const int baseUnit = settings()->gridUnit();\n    switch (m_internalSettings->buttonSize()) {\n    case InternalSettings::ButtonTiny:\n        return qRound(baseUnit * 0.1);\n    case InternalSettings::ButtonSmall:\n        return qRound(baseUnit * 0.2);\n    default:\n    case InternalSettings::ButtonDefault:\n        return qRound(baseUnit * 0.3);\n    case InternalSettings::ButtonLarge:\n        return qRound(baseUnit * 0.5);\n    case InternalSettings::ButtonVeryLarge:\n        return qRound(baseUnit * 0.8);\n    }\n}\n\nint Decoration::titleBarHeight() const\n{\n    const QFontMetrics fontMetrics(settings()->font());\n    return buttonPadding()*2 + fontMetrics.height();\n}\n\nint Decoration::appMenuButtonHorzPadding() const\n{\n    // smallSpacing is scaled by dpr with a min of 2px.\n    // So we need to divide our \"pixel units\" by 2 before scaling by it.\n    return settings()->smallSpacing() * m_internalSettings->menuButtonHorzPadding() / 2;\n}\n\nint Decoration::appMenuCaptionSpacing() const\n{\n    return settings()->largeSpacing() * 4;\n}\n\nint Decoration::captionMinWidth() const\n{\n    return settings()->largeSpacing() * 8;\n}\n\nint Decoration::bottomBorderSize() const {\n    const int baseSize = settings()->smallSpacing();\n    switch (settings()->borderSize()) {\n        default:\n        case KDecoration2::BorderSize::None:\n            return 0;\n        case KDecoration2::BorderSize::NoSides:\n        case KDecoration2::BorderSize::Tiny:\n            return 1; // Breeze: max(4, baseSize)\n        case KDecoration2::BorderSize::Normal:\n            return baseSize; // Breeze: baseSize*2\n        case KDecoration2::BorderSize::Large:\n            return baseSize*2; // Breeze: baseSize*3\n        case KDecoration2::BorderSize::VeryLarge:\n            return baseSize*3; // Breeze: ...\n        case KDecoration2::BorderSize::Huge:\n            return baseSize*4;\n        case KDecoration2::BorderSize::VeryHuge:\n            return baseSize*5;\n        case KDecoration2::BorderSize::Oversized:\n            return baseSize*10; // Same as Breeze\n    }\n}\nint Decoration::sideBorderSize() const {\n    switch (settings()->borderSize()) {\n        case KDecoration2::BorderSize::NoSides:\n            return 0;\n        default:\n            return bottomBorderSize();\n    }\n}\n\nbool Decoration::Decoration::leftBorderVisible() const {\n    const auto *decoratedClient = client().toStrongRef().data();\n    return !decoratedClient->isMaximizedHorizontally()\n        && !decoratedClient->adjacentScreenEdges().testFlag(Qt::LeftEdge);\n}\nbool Decoration::rightBorderVisible() const {\n    const auto *decoratedClient = client().toStrongRef().data();\n    return !decoratedClient->isMaximizedHorizontally()\n        && !decoratedClient->adjacentScreenEdges().testFlag(Qt::RightEdge);\n}\nbool Decoration::topBorderVisible() const {\n    const auto *decoratedClient = client().toStrongRef().data();\n    return !decoratedClient->isMaximizedVertically()\n        && !decoratedClient->adjacentScreenEdges().testFlag(Qt::TopEdge);\n}\nbool Decoration::bottomBorderVisible() const {\n    const auto *decoratedClient = client().toStrongRef().data();\n    return !decoratedClient->isMaximizedVertically()\n        && !decoratedClient->adjacentScreenEdges().testFlag(Qt::BottomEdge)\n        && !decoratedClient->isShaded();\n}\n\nbool Decoration::titleBarIsHovered() const\n{\n    return sectionUnderMouse() == Qt::TitleBarArea;\n}\n\nint Decoration::getTextWidth(const QString text, bool showMnemonic) const\n{\n    const QFontMetrics fontMetrics(settings()->font());\n    const QRect textRect(titleBarRect());\n    int flags = showMnemonic ? Qt::TextShowMnemonic : Qt::TextHideMnemonic;\n    const QRect boundingRect = fontMetrics.boundingRect(textRect, flags, text);\n    return boundingRect.width();\n}\n\n//* scoped pointer convenience typedef\ntemplate <typename T> using ScopedPointer = QScopedPointer<T, QScopedPointerPodDeleter>;\n\nQPoint Decoration::windowPos() const\n{\n    const auto *decoratedClient = client().toStrongRef().data();\n    WId windowId = decoratedClient->windowId();\n\n    if (KWindowSystem::isPlatformX11()) {\n#if HAVE_X11\n        //--- From: BreezeSizeGrip.cpp\n        /*\n        get root position matching position\n        need to use xcb because the embedding of the widget\n        breaks QT's mapToGlobal and other methods\n        */\n        auto connection( QX11Info::connection() );\n        xcb_get_geometry_cookie_t cookie( xcb_get_geometry( connection, windowId ) );\n        ScopedPointer<xcb_get_geometry_reply_t> reply( xcb_get_geometry_reply( connection, cookie, nullptr ) );\n        if (reply) {\n            // translate coordinates\n            xcb_translate_coordinates_cookie_t coordCookie( xcb_translate_coordinates(\n                connection, windowId, reply.data()->root,\n                -reply.data()->border_width,\n                -reply.data()->border_width ) );\n\n            ScopedPointer< xcb_translate_coordinates_reply_t> coordReply( xcb_translate_coordinates_reply( connection, coordCookie, nullptr ) );\n\n            if (coordReply) {\n                return QPoint(coordReply.data()->dst_x, coordReply.data()->dst_y);\n            }\n        }\n#else\n        Q_UNUSED(windowId)\n#endif\n\n    } else if (KWindowSystem::isPlatformWayland()) {\n#if HAVE_Wayland\n        // TODO\n#endif\n    }\n\n    return QPoint(0, 0);\n}\n\nvoid Decoration::initDragMove(const QPoint pos)\n{\n    m_pressedPoint = pos;\n}\n\nvoid Decoration::resetDragMove()\n{\n    m_pressedPoint = QPoint();\n}\n\n\nbool Decoration::dragMoveTick(const QPoint pos)\n{\n    if (m_pressedPoint.isNull()) {\n        return false;\n    }\n\n    QPoint diff = pos - m_pressedPoint;\n    // qCDebug(category) << \"    diff\" << diff << \"mL\" << diff.manhattanLength() << \"sDD\" << QApplication::startDragDistance();\n    if (diff.manhattanLength() >= QApplication::startDragDistance()) {\n        sendMoveEvent(pos);\n        resetDragMove();\n        return true;\n    }\n    return false;\n}\n\nvoid Decoration::sendMoveEvent(const QPoint pos)\n{\n    const auto *decoratedClient = client().toStrongRef().data();\n    WId windowId = decoratedClient->windowId();\n\n    QPoint globalPos = windowPos()\n        - QPoint(0, titleBarHeight())\n        + pos;\n\n    if (KWindowSystem::isPlatformX11()) {\n#if HAVE_X11\n        //--- From: BreezeSizeGrip.cpp\n        auto connection(QX11Info::connection());\n\n\n        // move/resize atom\n        if (!m_moveResizeAtom) {\n            // create atom if not found\n            const QString atomName( \"_NET_WM_MOVERESIZE\" );\n            xcb_intern_atom_cookie_t cookie( xcb_intern_atom( connection, false, atomName.size(), qPrintable( atomName ) ) );\n            ScopedPointer<xcb_intern_atom_reply_t> reply( xcb_intern_atom_reply( connection, cookie, nullptr ) );\n            m_moveResizeAtom = reply ? reply->atom : 0;\n        }\n        if (!m_moveResizeAtom) {\n            return;\n        }\n\n        // button release event\n        xcb_button_release_event_t releaseEvent;\n        memset(&releaseEvent, 0, sizeof(releaseEvent));\n\n        releaseEvent.response_type = XCB_BUTTON_RELEASE;\n        releaseEvent.event =  windowId;\n        releaseEvent.child = XCB_WINDOW_NONE;\n        releaseEvent.root = QX11Info::appRootWindow();\n        releaseEvent.event_x = pos.x();\n        releaseEvent.event_y = pos.y();\n        releaseEvent.root_x = globalPos.x();\n        releaseEvent.root_y = globalPos.y();\n        releaseEvent.detail = XCB_BUTTON_INDEX_1;\n        releaseEvent.state = XCB_BUTTON_MASK_1;\n        releaseEvent.time = XCB_CURRENT_TIME;\n        releaseEvent.same_screen = true;\n        xcb_send_event(\n            connection,\n            false,\n            windowId,\n            XCB_EVENT_MASK_BUTTON_RELEASE,\n            reinterpret_cast<const char*>(&releaseEvent)\n        );\n\n        xcb_ungrab_pointer(connection, XCB_TIME_CURRENT_TIME);\n\n        // move resize event\n        xcb_client_message_event_t clientMessageEvent;\n        memset(&clientMessageEvent, 0, sizeof(clientMessageEvent));\n\n        clientMessageEvent.response_type = XCB_CLIENT_MESSAGE;\n        clientMessageEvent.type = m_moveResizeAtom;\n        clientMessageEvent.format = 32;\n        clientMessageEvent.window = windowId;\n        clientMessageEvent.data.data32[0] = globalPos.x();\n        clientMessageEvent.data.data32[1] = globalPos.y();\n        clientMessageEvent.data.data32[2] = 8; // _NET_WM_MOVERESIZE_MOVE\n        clientMessageEvent.data.data32[3] = Qt::LeftButton;\n        clientMessageEvent.data.data32[4] = 0;\n\n        xcb_send_event(\n            connection,\n            false,\n            QX11Info::appRootWindow(),\n            XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,\n            reinterpret_cast<const char*>(&clientMessageEvent)\n        );\n\n        xcb_flush(connection);\n#else\n        Q_UNUSED(windowId)\n        Q_UNUSED(globalPos)\n#endif\n\n    } else if (KWindowSystem::isPlatformWayland()) {\n#if HAVE_Wayland\n        // TODO\n#endif\n\n    } else {\n        // Not X11\n    }\n}\n\nvoid Decoration::paintFrameBackground(QPainter *painter, const QRect &repaintRegion) const\n{\n    Q_UNUSED(repaintRegion)\n\n    painter->save();\n\n    painter->fillRect(rect(), Qt::transparent);\n    painter->setRenderHint(QPainter::Antialiasing);\n    painter->setPen(Qt::NoPen);\n    painter->setBrush(borderColor());\n    painter->setClipRect(0, borderTop(), size().width(), size().height() - borderTop(), Qt::IntersectClip);\n    painter->drawRect(rect());\n\n    painter->restore();\n}\n\nQColor Decoration::borderColor() const\n{\n    const auto *decoratedClient = client().toStrongRef().data();\n    const auto group = decoratedClient->isActive()\n        ? KDecoration2::ColorGroup::Active\n        : KDecoration2::ColorGroup::Inactive;\n    const qreal opacity = decoratedClient->isActive()\n        ? m_internalSettings->activeOpacity()\n        : m_internalSettings->inactiveOpacity();\n    QColor color = decoratedClient->color(group, KDecoration2::ColorRole::Frame);\n    color.setAlphaF(opacity);\n    return color;\n}\n\nQColor Decoration::titleBarBackgroundColor() const\n{\n    const auto *decoratedClient = client().toStrongRef().data();\n    const auto group = decoratedClient->isActive()\n        ? KDecoration2::ColorGroup::Active\n        : KDecoration2::ColorGroup::Inactive;\n    const qreal opacity = decoratedClient->isActive()\n        ? m_internalSettings->activeOpacity()\n        : m_internalSettings->inactiveOpacity();\n    QColor color = decoratedClient->color(group, KDecoration2::ColorRole::TitleBar);\n    color.setAlphaF(opacity);\n    return color;\n}\n\nQColor Decoration::titleBarForegroundColor() const\n{\n    const auto *decoratedClient = client().toStrongRef().data();\n    const auto group = decoratedClient->isActive()\n        ? KDecoration2::ColorGroup::Active\n        : KDecoration2::ColorGroup::Inactive;\n    return decoratedClient->color(group, KDecoration2::ColorRole::Foreground);\n}\n\nvoid Decoration::paintTitleBarBackground(QPainter *painter, const QRect &repaintRegion) const\n{\n    Q_UNUSED(repaintRegion)\n\n    painter->save();\n    painter->setPen(Qt::NoPen);\n    painter->setBrush(titleBarBackgroundColor());\n    painter->drawRect(QRect(0, 0, size().width(), titleBarHeight()));\n    painter->restore();\n}\n\nvoid Decoration::paintCaption(QPainter *painter, const QRect &repaintRegion) const\n{\n    Q_UNUSED(repaintRegion)\n\n    if (m_internalSettings->titleAlignment() == InternalSettings::TitleHidden) {\n        return;\n    }\n\n    const auto *decoratedClient = client().toStrongRef().data();\n\n    const int textWidth = settings()->fontMetrics().boundingRect(decoratedClient->caption()).width();\n    const QRect textRect((size().width() - textWidth) / 2, 0, textWidth, titleBarHeight());\n\n    const bool appMenuVisible = !m_menuButtons->buttons().isEmpty();\n    const int menuButtonsWidth = m_menuButtons->geometry().width()\n        + (appMenuVisible ? appMenuCaptionSpacing() : 0);\n\n    const QRect availableRect = centerRect().adjusted(\n        (m_menuButtons->alwaysShow() ? menuButtonsWidth : 0),\n        0,\n        0,\n        0\n    );\n\n    QRect captionRect;\n    Qt::Alignment alignment;\n\n    switch (m_internalSettings->titleAlignment()) {\n        case InternalSettings::AlignLeft:\n            captionRect = availableRect;\n            alignment = Qt::AlignLeft | Qt::AlignVCenter;\n            break;\n\n        case InternalSettings::AlignRight:\n            captionRect = availableRect;\n            alignment = Qt::AlignRight | Qt::AlignVCenter;\n            break;\n\n        case InternalSettings::AlignCenter:\n            captionRect = availableRect;\n            alignment = Qt::AlignCenter;\n            break;\n\n        default:\n        case InternalSettings::AlignCenterFullWidth:\n            if (textRect.left() < availableRect.left()) {\n                captionRect = availableRect;\n                alignment = Qt::AlignLeft | Qt::AlignVCenter;\n            } else if (availableRect.right() < textRect.right()) {\n                captionRect = availableRect;\n                alignment = Qt::AlignRight | Qt::AlignVCenter;\n            } else {\n                captionRect = titleBarRect();\n                alignment = Qt::AlignCenter;\n            }\n            break;\n    }\n\n    const QString caption = painter->fontMetrics().elidedText(\n        decoratedClient->caption(), Qt::ElideMiddle, captionRect.width());\n\n    painter->save();\n    painter->setFont(settings()->font());\n\n    if (m_menuButtons->buttons().isEmpty()) {\n        painter->setPen(titleBarForegroundColor());\n    } else { // menuButtons is visible\n        const int menuRight = m_menuButtons->geometry().right();\n        const int textLeft = textRect.left();\n        const int textRight = textRect.right();\n        // qCDebug(category) << \"textLeft\" << textLeft << \"menuRight\" << menuRight;\n\n        if (!m_menuButtons->alwaysShow()) { // caption fades away revealing menu\n            painter->setOpacity(1.0 - m_menuButtons->opacity());\n            painter->setPen(titleBarForegroundColor());\n        } else if (m_menuButtons->overflowing()) { // hide caption leaving \"whitespace\" to easily grab.\n            painter->setPen(Qt::transparent);\n        } else if (textRight < menuRight) { // menuButtons completely coveres caption\n            painter->setPen(Qt::transparent);\n        } else if (textLeft < menuRight) { // menuButtons covers caption\n            const int fadeWidth = 10; // TODO: scale by dpi\n            const int x1 = menuRight;\n            const int x2 = qMin(x1+fadeWidth, textRight);\n            const float x1Ratio = (float)(x1-textLeft) / (float)textWidth;\n            const float x2Ratio = (float)(x2-textLeft) / (float)textWidth;\n            // qCDebug(category) << \"    \" << \"x2\" << x2 << \"x1R\" << x1Ratio << \"x2R\" << x2Ratio;\n            QLinearGradient gradient(textRect.topLeft(), textRect.bottomRight());\n            gradient.setColorAt(x1Ratio, Qt::transparent);\n            gradient.setColorAt(x2Ratio, titleBarForegroundColor());\n            QBrush brush(gradient);\n            QPen pen(brush, 1);\n            painter->setPen(pen);\n        } else { // caption is not covered by menuButtons\n            painter->setPen(titleBarForegroundColor());\n        }\n    }\n\n    painter->drawText(captionRect, alignment, caption);\n    painter->restore();\n}\n\nvoid Decoration::paintButtons(QPainter *painter, const QRect &repaintRegion) const\n{\n    m_leftButtons->paint(painter, repaintRegion);\n    m_rightButtons->paint(painter, repaintRegion);\n    m_menuButtons->paint(painter, repaintRegion);\n}\n\nvoid Decoration::paintOutline(QPainter *painter, const QRect &repaintRegion) const\n{\n    Q_UNUSED(repaintRegion)\n\n    // Simple 1px border outline\n    painter->save();\n    painter->setRenderHint(QPainter::Antialiasing, false);\n    painter->setBrush(Qt::NoBrush);\n    QColor outlineColor(titleBarForegroundColor());\n    outlineColor.setAlphaF(0.25);\n    painter->setPen(outlineColor);\n    painter->drawRect( rect().adjusted( 0, 0, -1, -1 ) );\n    painter->restore();\n}\n\n} // namespace Material\n"
  },
  {
    "path": "src/Decoration.h",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// own\n#include \"BuildConfig.h\"\n#include \"AppMenuButtonGroup.h\"\n#include \"InternalSettings.h\"\n\n// KDecoration\n#include <KDecoration2/Decoration>\n#include <KDecoration2/DecorationButton>\n#include <KDecoration2/DecorationButtonGroup>\n\n// Qt\n#include <QHoverEvent>\n#include <QMouseEvent>\n#include <QRectF>\n#include <QSharedPointer>\n#include <QWheelEvent>\n#include <QVariant>\n\n#if HAVE_X11\n#include <xcb/xcb.h>\n#endif\n\n\nnamespace Material\n{\n\nclass Button;\nclass TextButton;\nclass MenuOverflowButton;\n\nclass Decoration : public KDecoration2::Decoration\n{\n    Q_OBJECT\n\npublic:\n    Decoration(QObject *parent = nullptr, const QVariantList &args = QVariantList());\n    ~Decoration() override;\n\n    QRect titleBarRect() const;\n    QRect centerRect() const;\n\n    void paint(QPainter *painter, const QRect &repaintRegion) override;\n\npublic slots:\n    void init() override;\n    void reconfigure();\n\nprotected:\n    void hoverEnterEvent(QHoverEvent *event) override;\n    void hoverMoveEvent(QHoverEvent *event) override;\n    void hoverLeaveEvent(QHoverEvent *event) override;\n    void mousePressEvent(QMouseEvent *event) override;\n    void mouseReleaseEvent(QMouseEvent *event) override;\n    void wheelEvent(QWheelEvent *event) override;\n\nprivate slots:\n    void onSectionUnderMouseChanged(const Qt::WindowFrameSection value);\n\nprivate:\n    void updateBlur();\n    void updateBorders();\n    void updateResizeBorders();\n    void updateTitleBar();\n    void updateTitleBarHoverState();\n    void setButtonGroupHeight(KDecoration2::DecorationButtonGroup *buttonGroup, int buttonHeight);\n    void setButtonGroupHorzPadding(KDecoration2::DecorationButtonGroup *buttonGroup, int value);\n    void setButtonGroupVertPadding(KDecoration2::DecorationButtonGroup *buttonGroup, int value);\n    void updateButtonHeight();\n    void updateButtonsGeometry();\n    void setButtonGroupAnimation(KDecoration2::DecorationButtonGroup *buttonGroup, bool enabled, int duration);\n    void updateButtonAnimation();\n    void updateShadow();\n\n    bool menuAlwaysShow() const;\n    bool animationsEnabled() const;\n    int animationsDuration() const;\n    int buttonPadding() const;\n    int titleBarHeight() const;\n    int appMenuButtonHorzPadding() const;\n    int appMenuCaptionSpacing() const;\n    int captionMinWidth() const;\n\n    int bottomBorderSize() const;\n    int sideBorderSize() const;\n\n    bool leftBorderVisible() const;\n    bool rightBorderVisible() const;\n    bool topBorderVisible() const;\n    bool bottomBorderVisible() const;\n\n    bool titleBarIsHovered() const;\n    int getTextWidth(const QString text, bool showMnemonic = false) const;\n    QPoint windowPos() const;\n\n    void initDragMove(const QPoint pos);\n    void resetDragMove();\n    bool dragMoveTick(const QPoint pos);\n    void sendMoveEvent(const QPoint pos);\n\n    QColor borderColor() const;\n    QColor titleBarBackgroundColor() const;\n    QColor titleBarForegroundColor() const;\n\n    void paintFrameBackground(QPainter *painter, const QRect &repaintRegion) const;\n    void paintTitleBarBackground(QPainter *painter, const QRect &repaintRegion) const;\n    void paintCaption(QPainter *painter, const QRect &repaintRegion) const;\n    void paintButtons(QPainter *painter, const QRect &repaintRegion) const;\n    void paintOutline(QPainter *painter, const QRect &repaintRegion) const;\n\n    KDecoration2::DecorationButtonGroup *m_leftButtons;\n    KDecoration2::DecorationButtonGroup *m_rightButtons;\n    AppMenuButtonGroup *m_menuButtons;\n\n    QSharedPointer<InternalSettings> m_internalSettings;\n\n    QPoint m_pressedPoint;\n\n#if HAVE_X11\n    xcb_atom_t m_moveResizeAtom = 0;\n#endif\n\n    friend class AppMenuButtonGroup;\n    friend class Button;\n    friend class AppIconButton;\n    friend class AppMenuButton;\n    friend class TextButton;\n    // friend class MenuOverflowButton;\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/InternalSettings.kcfgc",
    "content": "File=InternalSettingsSchema.kcfg\nClassName=InternalSettings\nNameSpace=Material\nSingleton=false\nMutators=true\nGlobalEnums=true\n"
  },
  {
    "path": "src/InternalSettingsSchema.kcfg",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kcfg xmlns=\"http://www.kde.org/standards/kcfg/1.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/standards/kcfg/1.0/kcfg.xsd\">\n    <kcfgfile name=\"kdecoration_materialrc\"/>\n\n    <group name=\"Windeco\">\n\n        <!-- button size -->\n        <entry name=\"ButtonSize\" type=\"Enum\">\n            <choices>\n                <choice name=\"ButtonTiny\" />\n                <choice name=\"ButtonSmall\" />\n                <choice name=\"ButtonDefault\" />\n                <choice name=\"ButtonLarge\" />\n                <choice name=\"ButtonVeryLarge\" />\n            </choices>\n            <default>ButtonDefault</default>\n        </entry>\n\n        <!-- title alignment -->\n        <entry name=\"TitleAlignment\" type=\"Enum\">\n            <choices>\n                <choice name=\"AlignLeft\" />\n                <choice name=\"AlignCenter\" />\n                <choice name=\"AlignCenterFullWidth\" />\n                <choice name=\"AlignRight\" />\n                <choice name=\"TitleHidden\" />\n            </choices>\n            <default>AlignCenterFullWidth</default>\n        </entry>\n\n        <!-- opacity -->\n        <entry name=\"ActiveOpacity\" type=\"Double\">\n            <default>0.75</default>\n        </entry>\n        <entry name=\"InactiveOpacity\" type=\"Double\">\n            <default>0.85</default>\n        </entry>\n\n        <!-- menu -->\n        <entry name=\"MenuAlwaysShow\" type=\"Bool\">\n            <default>true</default>\n        </entry>\n        <entry name=\"MenuButtonHorzPadding\" type=\"Int\">\n            <default>4</default>\n        </entry>\n\n        <!-- animations -->\n        <entry name=\"AnimationsEnabled\" type=\"Bool\">\n            <default>true</default>\n        </entry>\n        <entry name=\"AnimationsDuration\" type=\"Int\">\n            <default>250</default>\n        </entry>\n\n        <!-- shadow -->\n        <entry name=\"ShadowSize\" type=\"Enum\">\n            <choices>\n                <choice name=\"ShadowNone\"/>\n                <choice name=\"ShadowSmall\"/>\n                <choice name=\"ShadowMedium\"/>\n                <choice name=\"ShadowLarge\"/>\n                <choice name=\"ShadowVeryLarge\"/>\n            </choices>\n            <default>ShadowVeryLarge</default>\n        </entry>\n        <entry name=\"ShadowColor\" type=\"Color\">\n            <default>33, 33, 33</default>\n        </entry>\n        <entry name=\"ShadowStrength\" type=\"Int\">\n            <default>255</default>\n            <min>25</min>\n            <max>255</max>\n        </entry>\n    </group>\n\n</kcfg>\n"
  },
  {
    "path": "src/KeepAboveButton.h",
    "content": "/*\n * Copyright (C) 2019 Zain Ahmad <zain.x.ahmad@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// own\n#include \"Button.h\"\n#include \"Material.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n\n// Qt\n#include <QPainter>\n\nnamespace Material\n{\n\nclass KeepAboveButton\n{\n\npublic:\n    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {\n        Q_UNUSED(decoratedClient)\n\n        button->setVisible(true);\n    }\n    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {\n        button->setPenWidth(painter, gridUnit, 1.25);\n\n        painter->translate( iconRect.topLeft() );\n        painter->drawPolyline(  QVector<QPointF> {\n            QPointF( 0.5, 4.75 ) * gridUnit,\n            QPointF( 5.0, 0.25 ) * gridUnit,\n            QPointF( 9.5, 4.75 ) * gridUnit\n        });\n\n        painter->drawPolyline(  QVector<QPointF> {\n            QPointF( 0.5, 9.75 ) * gridUnit,\n            QPointF( 5.0, 5.25 ) * gridUnit,\n            QPointF( 9.5, 9.75 ) * gridUnit\n        });\n    }\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/KeepBelowButton.h",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// own\n#include \"Button.h\"\n#include \"Material.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n\n// Qt\n#include <QPainter>\n\nnamespace Material\n{\n\nclass KeepBelowButton\n{\n\npublic:\n    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {\n        Q_UNUSED(decoratedClient)\n\n        button->setVisible(true);\n    }\n    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {\n        button->setPenWidth(painter, gridUnit, 1.25);\n\n        painter->translate( iconRect.topLeft() );\n        painter->drawPolyline(  QVector<QPointF> {\n            QPointF( 0.5, 0.25 ) * gridUnit,\n            QPointF( 5.0, 4.75 ) * gridUnit,\n            QPointF( 9.5, 0.25 ) * gridUnit\n        });\n\n        painter->drawPolyline(  QVector<QPointF> {\n            QPointF( 0.5, 5.25 ) * gridUnit,\n            QPointF( 5.0, 9.75 ) * gridUnit,\n            QPointF( 9.5, 5.25 ) * gridUnit\n        });\n    }\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/Material.h",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// Qt\n#include <QLoggingCategory>\n\n\nnamespace Material\n{\n    static const QLoggingCategory category(\"kdecoration.material\");\n    static const QString s_configFilename = QStringLiteral(\"kdecoration_materialrc\");\n\n    //--- Standard pen widths\n    namespace PenWidth\n    {\n        /* https://github.com/KDE/breeze/blob/master/kstyle/breeze.h#L164\n         * Using 1 instead of slightly more than 1 causes symbols drawn with\n         * pen strokes to look skewed. The exact amount added does not matter\n         * as long as it isn't too visible.\n         */\n        // The standard pen stroke width for symbols.\n        static constexpr qreal Symbol = 1.01;\n    }\n\n} // namespace Material\n"
  },
  {
    "path": "src/MaximizeButton.h",
    "content": "/*\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// own\n#include \"Button.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n\n// Qt\n#include <QPainter>\n\nnamespace Material\n{\n\nclass MaximizeButton\n{\n\npublic:\n    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {\n        QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::maximizeableChanged,\n                button, &Button::setVisible);\n\n        button->setVisible(decoratedClient->isMaximizeable());\n    }\n    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, qreal gridUnit) {\n        if (button->isChecked()) {\n            const int offset = qRound(gridUnit * 2);\n            // Outline of first square, \"on top\", aligned bottom left.\n            painter->drawPolygon(QVector<QPointF> {\n                iconRect.bottomLeft(),\n                iconRect.topLeft() + QPointF(0, offset),\n                iconRect.topRight() + QPointF(-offset, offset),\n                iconRect.bottomRight() + QPointF(-offset, 0)\n            });\n\n            // Partially occluded square, \"below\" first square, aligned top right.\n            painter->drawPolyline(QVector<QPointF> {\n                iconRect.topLeft() + QPointF(offset, offset),\n                iconRect.topLeft() + QPointF(offset, 0),\n                iconRect.topRight(),\n                iconRect.bottomRight() + QPointF(0, -offset),\n                iconRect.bottomRight() + QPointF(-offset, -offset)\n            });\n        } else {\n            painter->drawRect(iconRect);\n        }\n    }\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/MenuOverflowButton.cc",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2019 Zain Ahmad <zain.x.ahmad@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n// own\n#include \"MenuOverflowButton.h\"\n#include \"Material.h\"\n#include \"AppMenuButton.h\"\n#include \"Decoration.h\"\n#include \"ApplicationMenuButton.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n\n// Qt\n#include <QDebug>\n#include <QPainter>\n\n\nnamespace Material\n{\n\nMenuOverflowButton::MenuOverflowButton(Decoration *decoration, const int buttonIndex, QObject *parent)\n    : AppMenuButton(decoration, buttonIndex, parent)\n{\n    auto *decoratedClient = decoration->client().toStrongRef().data();\n\n    setVisible(decoratedClient->hasApplicationMenu());\n}\n\nMenuOverflowButton::~MenuOverflowButton()\n{\n}\n\nvoid MenuOverflowButton::paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit)\n{\n    ApplicationMenuButton::paintIcon(this, painter, iconRect, gridUnit);\n}\n\n} // namespace Material\n"
  },
  {
    "path": "src/MenuOverflowButton.h",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2019 Zain Ahmad <zain.x.ahmad@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// own\n#include \"AppMenuButton.h\"\n\nnamespace Material\n{\n\nclass Decoration;\n\nclass MenuOverflowButton : public AppMenuButton\n{\n    Q_OBJECT\n\npublic:\n    MenuOverflowButton(Decoration *decoration, const int buttonIndex, QObject *parent = nullptr);\n    ~MenuOverflowButton() override;\n\n    void paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit) override;\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/MinimizeButton.h",
    "content": "/*\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// own\n#include \"Button.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n\n// Qt\n#include <QPainter>\n\nnamespace Material\n{\n\nclass MinimizeButton\n{\n\npublic:\n    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {\n        QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::minimizeableChanged,\n                button, &Button::setVisible);\n\n        button->setVisible(decoratedClient->isMinimizeable());\n    }\n    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {\n        Q_UNUSED(button)\n        Q_UNUSED(gridUnit)\n\n        painter->drawLine(\n            iconRect.left(), iconRect.center().y(),\n            iconRect.right(), iconRect.center().y());\n    }\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/OnAllDesktopsButton.h",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// own\n#include \"Button.h\"\n#include \"Material.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n\n// Qt\n#include <QPainter>\n\nnamespace Material\n{\n\nclass OnAllDesktopsButton\n{\n\npublic:\n    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {\n        Q_UNUSED(decoratedClient)\n\n        button->setVisible(true);\n    }\n    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {\n        Q_UNUSED(gridUnit)\n        painter->setRenderHints(QPainter::Antialiasing, true);\n        button->setPenWidth(painter, gridUnit, 1.25);\n\n        int radius = qMin(iconRect.width(), iconRect.height()) / 2;\n        QPoint center(iconRect.center().toPoint());\n        painter->drawPolygon( QVector<QPointF> {\n            center + QPoint(-radius, 0),\n            center + QPoint(0, -radius),\n            center + QPoint(radius, 0),\n            center + QPoint(0, radius)\n        });\n    }\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/ShadeButton.h",
    "content": "/*\n * Copyright (C) 2021 Chris Holland <zrenfire@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// own\n#include \"Button.h\"\n#include \"Material.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n\n// Qt\n#include <QPainter>\n\nnamespace Material\n{\n\nclass ShadeButton\n{\n\npublic:\n    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {\n        QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::shadeableChanged,\n                button, &Button::setVisible);\n\n        button->setVisible(decoratedClient->isShadeable());\n    }\n    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {\n        painter->translate( iconRect.topLeft() );\n\n        if (button->isChecked()) {\n            button->setPenWidth(painter, gridUnit, 1.0);\n            painter->drawLine( \n                QPointF( 0, 2 ) * gridUnit,\n                QPointF( 10, 2 ) * gridUnit\n            );\n            button->setPenWidth(painter, gridUnit, 1.25);\n            painter->drawPolyline(  QVector<QPointF> {\n                QPointF( 0.5, 5.25 ) * gridUnit,\n                QPointF( 5.0, 9.75 ) * gridUnit,\n                QPointF( 9.5, 5.25 ) * gridUnit\n            });\n        } else {\n            button->setPenWidth(painter, gridUnit, 1.0);\n            painter->drawLine( \n                QPointF( 0, 2 ) * gridUnit,\n                QPointF( 10, 2 ) * gridUnit\n            );\n            button->setPenWidth(painter, gridUnit, 1.25);\n            painter->drawPolyline( QVector<QPointF> {\n                QPointF( 0.5, 9.75 ) * gridUnit,\n                QPointF( 5.0, 5.25 ) * gridUnit,\n                QPointF( 9.5, 9.75 ) * gridUnit\n            });\n        }\n    }\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/TextButton.cc",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2016 Kai Uwe Broulik <kde@privat.broulik.de>\n * Copyright (C) 2014 by Hugo Pereira Da Costa <hugo.pereira@free.fr>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n// own\n#include \"TextButton.h\"\n#include \"Material.h\"\n#include \"AppMenuButton.h\"\n#include \"Decoration.h\"\n\n// KDecoration\n#include <KDecoration2/DecoratedClient>\n#include <KDecoration2/DecorationSettings>\n\n// Qt\n#include <QDebug>\n#include <QAction>\n#include <QFontMetrics>\n#include <QPainter>\n\n\nnamespace Material\n{\n\nTextButton::TextButton(Decoration *decoration, const int buttonIndex, QObject *parent)\n    : AppMenuButton(decoration, buttonIndex, parent)\n    , m_action(nullptr)\n    , m_text(QStringLiteral(\"Menu\"))\n{\n    const auto *deco = qobject_cast<Decoration *>(decoration);\n\n    setHorzPadding(deco->appMenuButtonHorzPadding());\n\n    setVisible(true);\n}\n\nTextButton::~TextButton()\n{\n}\n\nvoid TextButton::paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit)\n{\n    Q_UNUSED(iconRect)\n    Q_UNUSED(gridUnit)\n\n    // Font\n    painter->setFont(decoration()->settings()->font());\n\n    // TODO: Use Qt::TextShowMnemonic when Alt is pressed\n    const bool isAltPressed = false;\n    const Qt::TextFlag mnemonicFlag = isAltPressed ? Qt::TextShowMnemonic : Qt::TextHideMnemonic;\n    painter->drawText(geometry(), mnemonicFlag | Qt::AlignCenter, m_text);\n}\n\nQSize TextButton::getTextSize()\n{\n    const auto *deco = qobject_cast<Decoration *>(decoration());\n    if (!deco) {\n        return QSize(0, 0);\n    }\n\n    // const QString elidedText = painter->fontMetrics().elidedText(\n    //     m_text,\n    //     Qt::ElideRight,\n    //     100, // Max width TODO: scale by dpi\n    // );\n    const int textWidth = deco->getTextWidth(m_text);\n    const int titleBarHeight = deco->titleBarHeight();\n    const QSize size(textWidth, titleBarHeight);\n    return size;\n}\n\nQAction* TextButton::action() const\n{\n    return m_action;\n}\n\nvoid TextButton::setAction(QAction *set)\n{\n    if (m_action != set) {\n        m_action = set;\n        emit actionChanged();\n    }\n}\n\nQString TextButton::text() const\n{\n    return m_text;\n}\n\nvoid TextButton::setText(const QString set)\n{\n    if (m_text != set) {\n        m_text = set;\n        emit textChanged();\n\n        updateGeometry();\n    }\n}\n\nvoid TextButton::setHeight(int buttonHeight)\n{\n    Q_UNUSED(buttonHeight)\n\n    updateGeometry();\n}\n\nvoid TextButton::updateGeometry()\n{\n    const QSize textSize = getTextSize();\n    updateSize(textSize.width(), textSize.height());\n}\n\n\n} // namespace Material\n"
  },
  {
    "path": "src/TextButton.h",
    "content": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n// own\n#include \"AppMenuButton.h\"\n\n// Qt\n#include <QAction>\n\nnamespace Material\n{\n\nclass Decoration;\n\nclass TextButton : public AppMenuButton\n{\n    Q_OBJECT\n\npublic:\n    TextButton(Decoration *decoration, const int buttonIndex, QObject *parent = nullptr);\n    ~TextButton() override;\n\n    Q_PROPERTY(QAction* action READ action WRITE setAction NOTIFY actionChanged)\n    Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)\n\n    void paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit) override;\n    QSize getTextSize();\n\n\n    QAction* action() const;\n    void setAction(QAction *set);\n\n    QString text() const;\n    void setText(const QString set);\n\n    void setHeight(int buttonHeight) override;\n    void updateGeometry();\n\nsignals:\n    void actionChanged();\n    void textChanged();\n\nprivate:\n    QAction *m_action;\n    QString m_text;\n};\n\n} // namespace Material\n"
  },
  {
    "path": "src/libdbusmenuqt/CMakeLists.txt",
    "content": "set(libdbusmenu_SRCS\n    dbusmenuimporter.cpp\n    dbusmenushortcut_p.cpp\n    dbusmenutypes_p.cpp\n    utils.cpp\n)\n\necm_qt_declare_logging_category(libdbusmenu_SRCS\n    HEADER debug.h\n    IDENTIFIER DBUSMENUQT\n    CATEGORY_NAME org.kde.libdbusmenuqt\n    DEFAULT_SEVERITY Info\n)\n\nset_source_files_properties(com.canonical.dbusmenu.xml PROPERTIES\n    NO_NAMESPACE true\n    INCLUDE \"dbusmenutypes_p.h\"\n    CLASSNAME DBusMenuInterface\n)\n\nif (Qt6_FOUND)\n    qt_add_dbus_interface(libdbusmenu_SRCS com.canonical.dbusmenu.xml dbusmenu_interface)\nelseif(Qt5_FOUND)\n    qt5_add_dbus_interface(libdbusmenu_SRCS com.canonical.dbusmenu.xml dbusmenu_interface)\nendif()\n\n\n\nadd_library(dbusmenuqt STATIC ${libdbusmenu_SRCS})\ntarget_link_libraries(dbusmenuqt\n    Qt${QT_VERSION_MAJOR}::DBus\n    Qt${QT_VERSION_MAJOR}::Widgets\n)\n\nadd_subdirectory(test)\n"
  },
  {
    "path": "src/libdbusmenuqt/README",
    "content": "Contains a patched version of the import path of libdbusmenu-qt\nRemove when next version of libdbusmenu-qt is released."
  },
  {
    "path": "src/libdbusmenuqt/com.canonical.dbusmenu.xml",
    "content": "<interface name=\"com.canonical.dbusmenu\">\n    <property name=\"Version\" type=\"u\" access=\"read\"/>\n    <property name=\"Status\" type=\"s\" access=\"read\"/>\n    <signal name=\"ItemsPropertiesUpdated\">\n    <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out0\" value=\"DBusMenuItemList\"/>\n    <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out1\" value=\"DBusMenuItemKeysList\"/>\n    <arg type=\"a(ia{sv})\" direction=\"out\"/>\n    <arg type=\"a(ias)\" direction=\"out\"/>\n    </signal>\n    <signal name=\"LayoutUpdated\">\n    <arg name=\"revision\" type=\"u\" direction=\"out\"/>\n    <arg name=\"parentId\" type=\"i\" direction=\"out\"/>\n    </signal>\n    <signal name=\"ItemActivationRequested\">\n    <arg name=\"id\" type=\"i\" direction=\"out\"/>\n    <arg name=\"timeStamp\" type=\"u\" direction=\"out\"/>\n    </signal>\n    <method name=\"Event\">\n    <arg name=\"id\" type=\"i\" direction=\"in\"/>\n    <arg name=\"eventId\" type=\"s\" direction=\"in\"/>\n    <arg name=\"data\" type=\"v\" direction=\"in\"/>\n    <arg name=\"timestamp\" type=\"u\" direction=\"in\"/>\n    <annotation name=\"org.freedesktop.DBus.Method.NoReply\" value=\"true\"/>\n    </method>\n    <method name=\"GetProperty\">\n    <arg type=\"v\" direction=\"out\"/>\n    <arg name=\"id\" type=\"i\" direction=\"in\"/>\n    <arg name=\"property\" type=\"s\" direction=\"in\"/>\n    </method>\n    <method name=\"GetLayout\">\n    <arg type=\"u\" direction=\"out\"/>\n    <arg name=\"parentId\" type=\"i\" direction=\"in\"/>\n    <arg name=\"recursionDepth\" type=\"i\" direction=\"in\"/>\n    <arg name=\"propertyNames\" type=\"as\" direction=\"in\"/>\n    <arg name=\"item\" type=\"(ia{sv}av)\" direction=\"out\"/>\n    <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out1\" value=\"DBusMenuLayoutItem\"/>\n    </method>\n    <method name=\"GetGroupProperties\">\n    <arg type=\"a(ia{sv})\" direction=\"out\"/>\n    <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out0\" value=\"DBusMenuItemList\"/>\n    <arg name=\"ids\" type=\"ai\" direction=\"in\"/>\n    <annotation name=\"org.qtproject.QtDBus.QtTypeName.In0\" value=\"QList&lt;int&gt;\"/>\n    <arg name=\"propertyNames\" type=\"as\" direction=\"in\"/>\n    </method>\n    <method name=\"AboutToShow\">\n    <arg type=\"b\" direction=\"out\"/>\n    <arg name=\"id\" type=\"i\" direction=\"in\"/>\n    </method>\n</interface>\n"
  },
  {
    "path": "src/libdbusmenuqt/dbusmenuimporter.cpp",
    "content": "/* This file is part of the dbusmenu-qt library\n   Copyright 2009 Canonical\n   Author: Aurelien Gateau <aurelien.gateau@canonical.com>\n\n   This library is free software; you can redistribute it and/or\n   modify it under the terms of the GNU Library General Public\n   License (LGPL) as published by the Free Software Foundation;\n   either version 2 of the License, or (at your option) any later\n   version.\n\n   This library is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n   Library General Public License for more details.\n\n   You should have received a copy of the GNU Library General Public License\n   along with this library; see the file COPYING.LIB.  If not, write to\n   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n   Boston, MA 02110-1301, USA.\n*/\n#include \"dbusmenuimporter.h\"\n\n#include \"debug.h\"\n\n// Qt\n#include <QCoreApplication>\n#include <QDBusConnection>\n#include <QDBusInterface>\n#include <QDBusReply>\n#include <QDBusVariant>\n#include <QDebug>\n#include <QFont>\n#include <QMenu>\n#include <QPointer>\n#include <QSet>\n#include <QTime>\n#include <QTimer>\n#include <QToolButton>\n#include <QWidgetAction>\n\n// Local\n#include \"dbusmenushortcut_p.h\"\n#include \"dbusmenutypes_p.h\"\n#include \"utils_p.h\"\n\n// Generated\n#include \"dbusmenu_interface.h\"\n\n//#define BENCHMARK\n#ifdef BENCHMARK\nstatic QTime sChrono;\n#endif\n\n#define DMRETURN_IF_FAIL(cond)                                                                                                                                 \\\n    if (!(cond)) {                                                                                                                                             \\\n        qCWarning(DBUSMENUQT) << \"Condition failed: \" #cond;                                                                                                   \\\n        return;                                                                                                                                                \\\n    }\n\nstatic const char *DBUSMENU_PROPERTY_ID = \"_dbusmenu_id\";\nstatic const char *DBUSMENU_PROPERTY_ICON_NAME = \"_dbusmenu_icon_name\";\nstatic const char *DBUSMENU_PROPERTY_ICON_DATA_HASH = \"_dbusmenu_icon_data_hash\";\n\nstatic QAction *createKdeTitle(QAction *action, QWidget *parent)\n{\n    QToolButton *titleWidget = new QToolButton(nullptr);\n    QFont font = titleWidget->font();\n    font.setBold(true);\n    titleWidget->setFont(font);\n    titleWidget->setIcon(action->icon());\n    titleWidget->setText(action->text());\n    titleWidget->setDown(true);\n    titleWidget->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);\n\n    QWidgetAction *titleAction = new QWidgetAction(parent);\n    titleAction->setDefaultWidget(titleWidget);\n    return titleAction;\n}\n\nclass DBusMenuImporterPrivate\n{\npublic:\n    DBusMenuImporter *q;\n\n    DBusMenuInterface *m_interface;\n    QMenu *m_menu;\n    using ActionForId = QMap<int, QAction *>;\n    ActionForId m_actionForId;\n    QTimer *m_pendingLayoutUpdateTimer;\n\n    QSet<int> m_idsRefreshedByAboutToShow;\n    QSet<int> m_pendingLayoutUpdates;\n\n    QDBusPendingCallWatcher *refresh(int id)\n    {\n        auto call = m_interface->GetLayout(id, 1, QStringList());\n        QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, q);\n        watcher->setProperty(DBUSMENU_PROPERTY_ID, id);\n        QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q, &DBusMenuImporter::slotGetLayoutFinished);\n\n        return watcher;\n    }\n\n    QMenu *createMenu(QWidget *parent)\n    {\n        QMenu *menu = q->createMenu(parent);\n        return menu;\n    }\n\n    /**\n     * Init all the immutable action properties here\n     * TODO: Document immutable properties?\n     *\n     * Note: we remove properties we handle from the map (using QMap::take()\n     * instead of QMap::value()) to avoid warnings about these properties in\n     * updateAction()\n     */\n    QAction *createAction(int id, const QVariantMap &_map, QWidget *parent)\n    {\n        QVariantMap map = _map;\n        QAction *action = new QAction(parent);\n        action->setProperty(DBUSMENU_PROPERTY_ID, id);\n\n        QString type = map.take(QStringLiteral(\"type\")).toString();\n        if (type == QLatin1String(\"separator\")) {\n            action->setSeparator(true);\n        }\n\n        if (map.take(QStringLiteral(\"children-display\")).toString() == QLatin1String(\"submenu\")) {\n            QMenu *menu = createMenu(parent);\n            action->setMenu(menu);\n        }\n\n        QString toggleType = map.take(QStringLiteral(\"toggle-type\")).toString();\n        if (!toggleType.isEmpty()) {\n            action->setCheckable(true);\n            if (toggleType == QLatin1String(\"radio\")) {\n                QActionGroup *group = new QActionGroup(action);\n                group->addAction(action);\n            }\n        }\n\n        bool isKdeTitle = map.take(QStringLiteral(\"x-kde-title\")).toBool();\n        updateAction(action, map, map.keys());\n\n        if (isKdeTitle) {\n            action = createKdeTitle(action, parent);\n        }\n\n        return action;\n    }\n\n    /**\n     * Update mutable properties of an action. A property may be listed in\n     * requestedProperties but not in map, this means we should use the default value\n     * for this property.\n     *\n     * @param action the action to update\n     * @param map holds the property values\n     * @param requestedProperties which properties has been requested\n     */\n    void updateAction(QAction *action, const QVariantMap &map, const QStringList &requestedProperties)\n    {\n        Q_FOREACH (const QString &key, requestedProperties) {\n            updateActionProperty(action, key, map.value(key));\n        }\n    }\n\n    void updateActionProperty(QAction *action, const QString &key, const QVariant &value)\n    {\n        if (key == QLatin1String(\"label\")) {\n            updateActionLabel(action, value);\n        } else if (key == QLatin1String(\"enabled\")) {\n            updateActionEnabled(action, value);\n        } else if (key == QLatin1String(\"toggle-state\")) {\n            updateActionChecked(action, value);\n        } else if (key == QLatin1String(\"icon-name\")) {\n            updateActionIconByName(action, value);\n        } else if (key == QLatin1String(\"icon-data\")) {\n            updateActionIconByData(action, value);\n        } else if (key == QLatin1String(\"visible\")) {\n            updateActionVisible(action, value);\n        } else if (key == QLatin1String(\"shortcut\")) {\n            updateActionShortcut(action, value);\n        } else {\n            qDebug(DBUSMENUQT) << \"Unhandled property update\" << key;\n        }\n    }\n\n    void updateActionLabel(QAction *action, const QVariant &value)\n    {\n        QString text = swapMnemonicChar(value.toString(), '_', '&');\n        action->setText(text);\n    }\n\n    void updateActionEnabled(QAction *action, const QVariant &value)\n    {\n        action->setEnabled(value.isValid() ? value.toBool() : true);\n    }\n\n    void updateActionChecked(QAction *action, const QVariant &value)\n    {\n        if (action->isCheckable() && value.isValid()) {\n            action->setChecked(value.toInt() == 1);\n        }\n    }\n\n    void updateActionIconByName(QAction *action, const QVariant &value)\n    {\n        const QString iconName = value.toString();\n        const QString previous = action->property(DBUSMENU_PROPERTY_ICON_NAME).toString();\n        if (previous == iconName) {\n            return;\n        }\n        action->setProperty(DBUSMENU_PROPERTY_ICON_NAME, iconName);\n        if (iconName.isEmpty()) {\n            action->setIcon(QIcon());\n            return;\n        }\n        action->setIcon(q->iconForName(iconName));\n    }\n\n    void updateActionIconByData(QAction *action, const QVariant &value)\n    {\n        const QByteArray data = value.toByteArray();\n        uint dataHash = qHash(data);\n        uint previousDataHash = action->property(DBUSMENU_PROPERTY_ICON_DATA_HASH).toUInt();\n        if (previousDataHash == dataHash) {\n            return;\n        }\n        action->setProperty(DBUSMENU_PROPERTY_ICON_DATA_HASH, dataHash);\n        QPixmap pix;\n        if (!pix.loadFromData(data)) {\n            qDebug(DBUSMENUQT) << \"Failed to decode icon-data property for action\" << action->text();\n            action->setIcon(QIcon());\n            return;\n        }\n        action->setIcon(QIcon(pix));\n    }\n\n    void updateActionVisible(QAction *action, const QVariant &value)\n    {\n        action->setVisible(value.isValid() ? value.toBool() : true);\n    }\n\n    void updateActionShortcut(QAction *action, const QVariant &value)\n    {\n        QDBusArgument arg = value.value<QDBusArgument>();\n        DBusMenuShortcut dmShortcut;\n        arg >> dmShortcut;\n        QKeySequence keySequence = dmShortcut.toKeySequence();\n        action->setShortcut(keySequence);\n    }\n\n    QMenu *menuForId(int id) const\n    {\n        if (id == 0) {\n            return q->menu();\n        }\n        QAction *action = m_actionForId.value(id);\n        if (!action) {\n            return nullptr;\n        }\n        return action->menu();\n    }\n\n    void slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList);\n\n    void sendEvent(int id, const QString &eventId)\n    {\n        m_interface->Event(id, eventId, QDBusVariant(QString()), 0u);\n    }\n};\n\nDBusMenuImporter::DBusMenuImporter(const QString &service, const QString &path, QObject *parent)\n    : QObject(parent)\n    , d(new DBusMenuImporterPrivate)\n{\n    DBusMenuTypes_register();\n\n    d->q = this;\n    d->m_interface = new DBusMenuInterface(service, path, QDBusConnection::sessionBus(), this);\n    d->m_menu = nullptr;\n\n    d->m_pendingLayoutUpdateTimer = new QTimer(this);\n    d->m_pendingLayoutUpdateTimer->setSingleShot(true);\n    connect(d->m_pendingLayoutUpdateTimer, &QTimer::timeout, this, &DBusMenuImporter::processPendingLayoutUpdates);\n\n    connect(d->m_interface, &DBusMenuInterface::LayoutUpdated, this, &DBusMenuImporter::slotLayoutUpdated);\n    connect(d->m_interface, &DBusMenuInterface::ItemActivationRequested, this, &DBusMenuImporter::slotItemActivationRequested);\n    connect(d->m_interface,\n            &DBusMenuInterface::ItemsPropertiesUpdated,\n            this,\n            [this](const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList) {\n                d->slotItemsPropertiesUpdated(updatedList, removedList);\n            });\n\n    d->refresh(0);\n}\n\nDBusMenuImporter::~DBusMenuImporter()\n{\n    // Do not use \"delete d->m_menu\": even if we are being deleted we should\n    // leave enough time for the menu to finish what it was doing, for example\n    // if it was being displayed.\n    d->m_menu->deleteLater();\n    delete d;\n}\n\nvoid DBusMenuImporter::slotLayoutUpdated(uint revision, int parentId)\n{\n    Q_UNUSED(revision)\n    if (d->m_idsRefreshedByAboutToShow.remove(parentId)) {\n        return;\n    }\n    d->m_pendingLayoutUpdates << parentId;\n    if (!d->m_pendingLayoutUpdateTimer->isActive()) {\n        d->m_pendingLayoutUpdateTimer->start();\n    }\n}\n\nvoid DBusMenuImporter::processPendingLayoutUpdates()\n{\n    QSet<int> ids = d->m_pendingLayoutUpdates;\n    d->m_pendingLayoutUpdates.clear();\n    Q_FOREACH (int id, ids) {\n        d->refresh(id);\n    }\n}\n\nQMenu *DBusMenuImporter::menu() const\n{\n    if (!d->m_menu) {\n        d->m_menu = d->createMenu(nullptr);\n    }\n    return d->m_menu;\n}\n\nvoid DBusMenuImporterPrivate::slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList)\n{\n    Q_FOREACH (const DBusMenuItem &item, updatedList) {\n        QAction *action = m_actionForId.value(item.id);\n        if (!action) {\n            // We don't know this action. It probably is in a menu we haven't fetched yet.\n            continue;\n        }\n\n        QVariantMap::ConstIterator it = item.properties.constBegin(), end = item.properties.constEnd();\n        for (; it != end; ++it) {\n            updateActionProperty(action, it.key(), it.value());\n        }\n    }\n\n    Q_FOREACH (const DBusMenuItemKeys &item, removedList) {\n        QAction *action = m_actionForId.value(item.id);\n        if (!action) {\n            // We don't know this action. It probably is in a menu we haven't fetched yet.\n            continue;\n        }\n\n        Q_FOREACH (const QString &key, item.properties) {\n            updateActionProperty(action, key, QVariant());\n        }\n    }\n}\n\nQAction *DBusMenuImporter::actionForId(int id) const\n{\n    return d->m_actionForId.value(id);\n}\n\nvoid DBusMenuImporter::slotItemActivationRequested(int id, uint /*timestamp*/)\n{\n    QAction *action = d->m_actionForId.value(id);\n    DMRETURN_IF_FAIL(action);\n    actionActivationRequested(action);\n}\n\nvoid DBusMenuImporter::slotGetLayoutFinished(QDBusPendingCallWatcher *watcher)\n{\n    int parentId = watcher->property(DBUSMENU_PROPERTY_ID).toInt();\n    watcher->deleteLater();\n\n    QMenu *menu = d->menuForId(parentId);\n\n    QDBusPendingReply<uint, DBusMenuLayoutItem> reply = *watcher;\n    if (!reply.isValid()) {\n        qDebug(DBUSMENUQT) << reply.error().message();\n        if (menu) {\n            emit menuUpdated(menu);\n        }\n        return;\n    }\n\n#ifdef BENCHMARK\n    DMDEBUG << \"- items received:\" << sChrono.elapsed() << \"ms\";\n#endif\n    DBusMenuLayoutItem rootItem = reply.argumentAt<1>();\n\n    if (!menu) {\n        qDebug(DBUSMENUQT) << \"No menu for id\" << parentId;\n        return;\n    }\n\n    // remove outdated actions\n    QSet<int> newDBusMenuItemIds;\n    newDBusMenuItemIds.reserve(rootItem.children.count());\n    for (const DBusMenuLayoutItem &item : qAsConst(rootItem.children)) {\n        newDBusMenuItemIds << item.id;\n    }\n    for (QAction *action : menu->actions()) {\n        int id = action->property(DBUSMENU_PROPERTY_ID).toInt();\n        if (!newDBusMenuItemIds.contains(id)) {\n            // Not calling removeAction() as QMenu will immediately close when it becomes empty,\n            // which can happen when an application completely reloads this menu.\n            // When the action is deleted deferred, it is removed from the menu.\n            action->deleteLater();\n            if (action->menu()) {\n                action->menu()->deleteLater();\n            }\n            d->m_actionForId.remove(id);\n        }\n    }\n\n    // insert or update new actions into our menu\n    for (const DBusMenuLayoutItem &dbusMenuItem : qAsConst(rootItem.children)) {\n        DBusMenuImporterPrivate::ActionForId::Iterator it = d->m_actionForId.find(dbusMenuItem.id);\n        QAction *action = nullptr;\n        if (it == d->m_actionForId.end()) {\n            int id = dbusMenuItem.id;\n            action = d->createAction(id, dbusMenuItem.properties, menu);\n            d->m_actionForId.insert(id, action);\n\n            connect(action, &QObject::destroyed, this, [this, id]() {\n                d->m_actionForId.remove(id);\n            });\n\n            connect(action, &QAction::triggered, this, [id, this]() {\n                sendClickedEvent(id);\n            });\n\n            if (QMenu *menuAction = action->menu()) {\n                connect(menuAction, &QMenu::aboutToShow, this, &DBusMenuImporter::slotMenuAboutToShow, Qt::UniqueConnection);\n            }\n            connect(menu, &QMenu::aboutToHide, this, &DBusMenuImporter::slotMenuAboutToHide, Qt::UniqueConnection);\n\n            menu->addAction(action);\n        } else {\n            action = *it;\n            QStringList filteredKeys = dbusMenuItem.properties.keys();\n            filteredKeys.removeOne(\"type\");\n            filteredKeys.removeOne(\"toggle-type\");\n            filteredKeys.removeOne(\"children-display\");\n            d->updateAction(*it, dbusMenuItem.properties, filteredKeys);\n            // Move the action to the tail so we can keep the order same as the dbus request.\n            menu->removeAction(action);\n            menu->addAction(action);\n        }\n    }\n\n    emit menuUpdated(menu);\n}\n\nvoid DBusMenuImporter::sendClickedEvent(int id)\n{\n    d->sendEvent(id, QStringLiteral(\"clicked\"));\n}\n\nvoid DBusMenuImporter::updateMenu()\n{\n    updateMenu(DBusMenuImporter::menu());\n}\n\nvoid DBusMenuImporter::updateMenu(QMenu *menu)\n{\n    Q_ASSERT(menu);\n\n    QAction *action = menu->menuAction();\n    Q_ASSERT(action);\n\n    int id = action->property(DBUSMENU_PROPERTY_ID).toInt();\n\n    auto call = d->m_interface->AboutToShow(id);\n    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);\n    watcher->setProperty(DBUSMENU_PROPERTY_ID, id);\n    connect(watcher, &QDBusPendingCallWatcher::finished, this, &DBusMenuImporter::slotAboutToShowDBusCallFinished);\n\n    // Firefox deliberately ignores \"aboutToShow\" whereas Qt ignores\" opened\", so we'll just send both all the time...\n    d->sendEvent(id, QStringLiteral(\"opened\"));\n}\n\nvoid DBusMenuImporter::slotAboutToShowDBusCallFinished(QDBusPendingCallWatcher *watcher)\n{\n    int id = watcher->property(DBUSMENU_PROPERTY_ID).toInt();\n    watcher->deleteLater();\n\n    QMenu *menu = d->menuForId(id);\n    if (!menu) {\n        return;\n    }\n\n    QDBusPendingReply<bool> reply = *watcher;\n    if (reply.isError()) {\n        qDebug(DBUSMENUQT) << \"Call to AboutToShow() failed:\" << reply.error().message();\n        Q_EMIT menuUpdated(menu);\n        return;\n    }\n    // Note, this isn't used by Qt's QPT - but we get a LayoutChanged emitted before\n    // this returns, which equates to the same thing\n    bool needRefresh = reply.argumentAt<0>();\n\n    if (needRefresh || menu->actions().isEmpty()) {\n        d->m_idsRefreshedByAboutToShow << id;\n        d->refresh(id);\n    } else if (menu) {\n        Q_EMIT menuUpdated(menu);\n    }\n}\n\nvoid DBusMenuImporter::slotMenuAboutToHide()\n{\n    QMenu *menu = qobject_cast<QMenu *>(sender());\n    Q_ASSERT(menu);\n\n    QAction *action = menu->menuAction();\n    Q_ASSERT(action);\n\n    int id = action->property(DBUSMENU_PROPERTY_ID).toInt();\n    d->sendEvent(id, QStringLiteral(\"closed\"));\n}\n\nvoid DBusMenuImporter::slotMenuAboutToShow()\n{\n    QMenu *menu = qobject_cast<QMenu *>(sender());\n    Q_ASSERT(menu);\n\n    updateMenu(menu);\n}\n\nQMenu *DBusMenuImporter::createMenu(QWidget *parent)\n{\n    return new QMenu(parent);\n}\n\nQIcon DBusMenuImporter::iconForName(const QString & /*name*/)\n{\n    return QIcon();\n}\n\n#include \"moc_dbusmenuimporter.cpp\"\n"
  },
  {
    "path": "src/libdbusmenuqt/dbusmenuimporter.h",
    "content": "/* This file is part of the dbusmenu-qt library\n   Copyright 2009 Canonical\n   Author: Aurelien Gateau <aurelien.gateau@canonical.com>\n\n   This library is free software; you can redistribute it and/or\n   modify it under the terms of the GNU Library General Public\n   License (LGPL) as published by the Free Software Foundation;\n   either version 2 of the License, or (at your option) any later\n   version.\n\n   This library is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n   Library General Public License for more details.\n\n   You should have received a copy of the GNU Library General Public License\n   along with this library; see the file COPYING.LIB.  If not, write to\n   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n   Boston, MA 02110-1301, USA.\n*/\n#ifndef DBUSMENUIMPORTER_H\n#define DBUSMENUIMPORTER_H\n\n// Qt\n#include <QObject>\n\nclass QAction;\nclass QDBusPendingCallWatcher;\nclass QIcon;\nclass QMenu;\n\nclass DBusMenuImporterPrivate;\n\n/**\n * A DBusMenuImporter instance can recreate a menu serialized over DBus by\n * DBusMenuExporter\n */\nclass DBusMenuImporter : public QObject\n{\n    Q_OBJECT\npublic:\n    /**\n     * Creates a DBusMenuImporter listening over DBus on service, path\n     */\n    DBusMenuImporter(const QString &service, const QString &path, QObject *parent = nullptr);\n\n    ~DBusMenuImporter() override;\n\n    QAction *actionForId(int id) const;\n\n    /**\n     * The menu created from listening to the DBusMenuExporter over DBus\n     */\n    QMenu *menu() const;\n\npublic Q_SLOTS:\n    /**\n     * Load the menu\n     *\n     * Will emit menuUpdated() when complete.\n     * This should be done before showing a menu\n     */\n    void updateMenu();\n\n    void updateMenu(QMenu *menu);\n\nQ_SIGNALS:\n    /**\n     * Emitted after a call to updateMenu().\n     * @see updateMenu()\n     */\n    void menuUpdated(QMenu *);\n\n    /**\n     * Emitted when the exporter was asked to activate an action\n     */\n    void actionActivationRequested(QAction *);\n\nprotected:\n    /**\n     * Must create a menu, may be customized to fit host appearance.\n     * Default implementation creates a simple QMenu.\n     */\n    virtual QMenu *createMenu(QWidget *parent);\n\n    /**\n     * Must convert a name into an icon.\n     * Default implementation returns a null icon.\n     */\n    virtual QIcon iconForName(const QString &);\n\nprivate Q_SLOTS:\n    void sendClickedEvent(int);\n    void slotMenuAboutToShow();\n    void slotMenuAboutToHide();\n    void slotAboutToShowDBusCallFinished(QDBusPendingCallWatcher *);\n    void slotItemActivationRequested(int id, uint timestamp);\n    void processPendingLayoutUpdates();\n    void slotLayoutUpdated(uint revision, int parentId);\n    void slotGetLayoutFinished(QDBusPendingCallWatcher *);\n\nprivate:\n    Q_DISABLE_COPY(DBusMenuImporter)\n    DBusMenuImporterPrivate *const d;\n    friend class DBusMenuImporterPrivate;\n\n    // Use Q_PRIVATE_SLOT to avoid exposing DBusMenuItemList\n    Q_PRIVATE_SLOT(d, void slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList))\n};\n\n#endif /* DBUSMENUIMPORTER_H */\n"
  },
  {
    "path": "src/libdbusmenuqt/dbusmenushortcut_p.cpp",
    "content": "/* This file is part of the dbusmenu-qt library\n   Copyright 2009 Canonical\n   Author: Aurelien Gateau <aurelien.gateau@canonical.com>\n\n   This library is free software; you can redistribute it and/or\n   modify it under the terms of the GNU Library General Public\n   License (LGPL) as published by the Free Software Foundation;\n   either version 2 of the License, or (at your option) any later\n   version.\n\n   This library is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n   Library General Public License for more details.\n\n   You should have received a copy of the GNU Library General Public License\n   along with this library; see the file COPYING.LIB.  If not, write to\n   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n   Boston, MA 02110-1301, USA.\n*/\n#include \"dbusmenushortcut_p.h\"\n\n// Qt\n#include <QKeySequence>\n\nstatic const int QT_COLUMN = 0;\nstatic const int DM_COLUMN = 1;\n\nstatic void processKeyTokens(QStringList *tokens, int srcCol, int dstCol)\n{\n    struct Row {\n        const char *zero;\n        const char *one;\n        const char *operator[](int col) const\n        {\n            return col == 0 ? zero : one;\n        }\n    };\n    static const Row table[] = {{\"Meta\", \"Super\"},\n                                {\"Ctrl\", \"Control\"},\n                                // Special cases for compatibility with libdbusmenu-glib which uses\n                                // \"plus\" for \"+\" and \"minus\" for \"-\".\n                                // cf https://bugs.launchpad.net/libdbusmenu-qt/+bug/712565\n                                {\"+\", \"plus\"},\n                                {\"-\", \"minus\"},\n                                {nullptr, nullptr}};\n\n    const Row *ptr = table;\n    for (; ptr->zero != nullptr; ++ptr) {\n        const char *from = (*ptr)[srcCol];\n        const char *to = (*ptr)[dstCol];\n        tokens->replaceInStrings(from, to);\n    }\n}\n\nDBusMenuShortcut DBusMenuShortcut::fromKeySequence(const QKeySequence &sequence)\n{\n    QString string = sequence.toString();\n    DBusMenuShortcut shortcut;\n    QStringList tokens = string.split(QStringLiteral(\", \"));\n    Q_FOREACH (QString token, tokens) {\n        // Hack: Qt::CTRL | Qt::Key_Plus is turned into the string \"Ctrl++\",\n        // but we don't want the call to token.split() to consider the\n        // second '+' as a separator so we replace it with its final value.\n        token.replace(QLatin1String(\"++\"), QLatin1String(\"+plus\"));\n        QStringList keyTokens = token.split('+');\n        processKeyTokens(&keyTokens, QT_COLUMN, DM_COLUMN);\n        shortcut << keyTokens;\n    }\n    return shortcut;\n}\n\nQKeySequence DBusMenuShortcut::toKeySequence() const\n{\n    QStringList tmp;\n    Q_FOREACH (const QStringList &keyTokens_, *this) {\n        QStringList keyTokens = keyTokens_;\n        processKeyTokens(&keyTokens, DM_COLUMN, QT_COLUMN);\n        tmp << keyTokens.join(QLatin1String(\"+\"));\n    }\n    QString string = tmp.join(QLatin1String(\", \"));\n    return QKeySequence::fromString(string);\n}\n"
  },
  {
    "path": "src/libdbusmenuqt/dbusmenushortcut_p.h",
    "content": "/* This file is part of the dbusmenu-qt library\n   Copyright 2009 Canonical\n   Author: Aurelien Gateau <aurelien.gateau@canonical.com>\n\n   This library is free software; you can redistribute it and/or\n   modify it under the terms of the GNU Library General Public\n   License (LGPL) as published by the Free Software Foundation;\n   either version 2 of the License, or (at your option) any later\n   version.\n\n   This library is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n   Library General Public License for more details.\n\n   You should have received a copy of the GNU Library General Public License\n   along with this library; see the file COPYING.LIB.  If not, write to\n   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n   Boston, MA 02110-1301, USA.\n*/\n#ifndef DBUSMENUSHORTCUT_H\n#define DBUSMENUSHORTCUT_H\n\n// Qt\n#include <QMetaType>\n#include <QStringList>\n\nclass QKeySequence;\n\nclass DBusMenuShortcut : public QList<QStringList>\n{\npublic:\n    QKeySequence toKeySequence() const;\n    static DBusMenuShortcut fromKeySequence(const QKeySequence &);\n};\n\nQ_DECLARE_METATYPE(DBusMenuShortcut)\n\n#endif /* DBUSMENUSHORTCUT_H */\n"
  },
  {
    "path": "src/libdbusmenuqt/dbusmenutypes_p.cpp",
    "content": "/* This file is part of the dbusmenu-qt library\n   Copyright 2009 Canonical\n   Author: Aurelien Gateau <aurelien.gateau@canonical.com>\n\n   This library is free software; you can redistribute it and/or\n   modify it under the terms of the GNU Library General Public\n   License (LGPL) as published by the Free Software Foundation;\n   either version 2 of the License, or (at your option) any later\n   version.\n\n   This library is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n   Library General Public License for more details.\n\n   You should have received a copy of the GNU Library General Public License\n   along with this library; see the file COPYING.LIB.  If not, write to\n   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n   Boston, MA 02110-1301, USA.\n*/\n#include \"dbusmenutypes_p.h\"\n\n// Local\n#include \"dbusmenushortcut_p.h\"\n\n// Qt\n#include <QDBusArgument>\n#include <QDBusMetaType>\n\n//// DBusMenuItem\nQDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItem &obj)\n{\n    argument.beginStructure();\n    argument << obj.id << obj.properties;\n    argument.endStructure();\n    return argument;\n}\n\nconst QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItem &obj)\n{\n    argument.beginStructure();\n    argument >> obj.id >> obj.properties;\n    argument.endStructure();\n    return argument;\n}\n\n//// DBusMenuItemKeys\nQDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItemKeys &obj)\n{\n    argument.beginStructure();\n    argument << obj.id << obj.properties;\n    argument.endStructure();\n    return argument;\n}\n\nconst QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItemKeys &obj)\n{\n    argument.beginStructure();\n    argument >> obj.id >> obj.properties;\n    argument.endStructure();\n    return argument;\n}\n\n//// DBusMenuLayoutItem\nQDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuLayoutItem &obj)\n{\n    argument.beginStructure();\n    argument << obj.id << obj.properties;\n    argument.beginArray(qMetaTypeId<QDBusVariant>());\n    Q_FOREACH (const DBusMenuLayoutItem &child, obj.children) {\n        argument << QDBusVariant(QVariant::fromValue<DBusMenuLayoutItem>(child));\n    }\n    argument.endArray();\n    argument.endStructure();\n    return argument;\n}\n\nconst QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuLayoutItem &obj)\n{\n    argument.beginStructure();\n    argument >> obj.id >> obj.properties;\n    argument.beginArray();\n    while (!argument.atEnd()) {\n        QDBusVariant dbusVariant;\n        argument >> dbusVariant;\n        QDBusArgument childArgument = dbusVariant.variant().value<QDBusArgument>();\n\n        DBusMenuLayoutItem child;\n        childArgument >> child;\n        obj.children.append(child);\n    }\n    argument.endArray();\n    argument.endStructure();\n    return argument;\n}\n\n//// DBusMenuShortcut\nQDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuShortcut &obj)\n{\n    argument.beginArray(qMetaTypeId<QStringList>());\n    typename QList<QStringList>::ConstIterator it = obj.constBegin();\n    typename QList<QStringList>::ConstIterator end = obj.constEnd();\n    for (; it != end; ++it)\n        argument << *it;\n    argument.endArray();\n    return argument;\n}\n\nconst QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuShortcut &obj)\n{\n    argument.beginArray();\n    obj.clear();\n    while (!argument.atEnd()) {\n        QStringList item;\n        argument >> item;\n        obj.push_back(item);\n    }\n    argument.endArray();\n    return argument;\n}\n\nvoid DBusMenuTypes_register()\n{\n    static bool registered = false;\n    if (registered) {\n        return;\n    }\n    qDBusRegisterMetaType<DBusMenuItem>();\n    qDBusRegisterMetaType<DBusMenuItemList>();\n    qDBusRegisterMetaType<DBusMenuItemKeys>();\n    qDBusRegisterMetaType<DBusMenuItemKeysList>();\n    qDBusRegisterMetaType<DBusMenuLayoutItem>();\n    qDBusRegisterMetaType<DBusMenuLayoutItemList>();\n    qDBusRegisterMetaType<DBusMenuShortcut>();\n    registered = true;\n}\n"
  },
  {
    "path": "src/libdbusmenuqt/dbusmenutypes_p.h",
    "content": "/* This file is part of the dbusmenu-qt library\n   Copyright 2009 Canonical\n   Author: Aurelien Gateau <aurelien.gateau@canonical.com>\n\n   This library is free software; you can redistribute it and/or\n   modify it under the terms of the GNU Library General Public\n   License (LGPL) as published by the Free Software Foundation;\n   either version 2 of the License, or (at your option) any later\n   version.\n\n   This library is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n   Library General Public License for more details.\n\n   You should have received a copy of the GNU Library General Public License\n   along with this library; see the file COPYING.LIB.  If not, write to\n   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n   Boston, MA 02110-1301, USA.\n*/\n#ifndef DBUSMENUTYPES_P_H\n#define DBUSMENUTYPES_P_H\n\n// Qt\n#include <QList>\n#include <QStringList>\n#include <QVariant>\n\nclass QDBusArgument;\n\n//// DBusMenuItem\n/**\n * Internal struct used to communicate on DBus\n */\nstruct DBusMenuItem {\n    int id;\n    QVariantMap properties;\n};\n\nQ_DECLARE_METATYPE(DBusMenuItem)\n\nQDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItem &item);\nconst QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItem &item);\n\ntypedef QList<DBusMenuItem> DBusMenuItemList;\n\nQ_DECLARE_METATYPE(DBusMenuItemList)\n\n//// DBusMenuItemKeys\n/**\n * Represents a list of keys for a menu item\n */\nstruct DBusMenuItemKeys {\n    int id;\n    QStringList properties;\n};\n\nQ_DECLARE_METATYPE(DBusMenuItemKeys)\n\nQDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItemKeys &);\nconst QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItemKeys &);\n\ntypedef QList<DBusMenuItemKeys> DBusMenuItemKeysList;\n\nQ_DECLARE_METATYPE(DBusMenuItemKeysList)\n\n//// DBusMenuLayoutItem\n/**\n * Represents an item with its children. GetLayout() returns a\n * DBusMenuLayoutItemList.\n */\nstruct DBusMenuLayoutItem;\nstruct DBusMenuLayoutItem {\n    int id;\n    QVariantMap properties;\n    QList<DBusMenuLayoutItem> children;\n};\n\nQ_DECLARE_METATYPE(DBusMenuLayoutItem)\n\nQDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuLayoutItem &);\nconst QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuLayoutItem &);\n\ntypedef QList<DBusMenuLayoutItem> DBusMenuLayoutItemList;\n\nQ_DECLARE_METATYPE(DBusMenuLayoutItemList)\n\n//// DBusMenuShortcut\n\nclass DBusMenuShortcut;\n\nQDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuShortcut &);\nconst QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuShortcut &);\n\nvoid DBusMenuTypes_register();\n#endif /* DBUSMENUTYPES_P_H */\n"
  },
  {
    "path": "src/libdbusmenuqt/test/CMakeLists.txt",
    "content": "add_executable(appmenutest main.cpp)\ntarget_link_libraries(appmenutest\n    Qt${QT_VERSION_MAJOR}::Widgets\n)\n"
  },
  {
    "path": "src/libdbusmenuqt/test/README",
    "content": "App with a menu, designed for use testing appmenu QPTs/applets/kded modules\nsmall enough that we can attach debuggers and breakpoints without drowning in data\n"
  },
  {
    "path": "src/libdbusmenuqt/test/main.cpp",
    "content": "/*\n *   Copyright 2017 David Edmundson <davidedmundson@kde.org>\n *   This program is free software; you can redistribute it and/or modify\n *   it under the terms of the GNU Library General Public License as\n *   published by the Free Software Foundation; either version 2, or\n *   (at your option) any later version.\n *\n *   This program is distributed in the hope that it will be useful,\n *   but WITHOUT ANY WARRANTY; without even the implied warranty of\n *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *   GNU General Public License for more details\n *\n *   You should have received a copy of the GNU Library General Public\n *   License along with this program; if not, write to the\n *   Free Software Foundation, Inc.,\n *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n\n#include <QApplication>\n\n#include <QDateTime>\n#include <QDebug>\n#include <QIcon>\n#include <QMainWindow>\n#include <QMenuBar>\n\nclass MainWindow : public QMainWindow\n{\npublic:\n    MainWindow();\n};\n\nMainWindow::MainWindow()\n    : QMainWindow()\n{\n    /*set an initial menu with the following\n    Menu A\n      - Item\n      - Checkable Item\n      - Item With Icon\n      - A separator\n      - Menu B\n         - Item B1\n     Menu C\n      - DynamicItem ${timestamp}\n\n      TopLevelItem\n    */\n\n    QAction *t;\n    auto menuA = new QMenu(\"Menu A\", this);\n    menuA->addAction(\"Item\");\n\n    t = menuA->addAction(\"Checkable Item\");\n    t->setCheckable(true);\n\n    t = menuA->addAction(QIcon::fromTheme(\"document-edit\"), \"Item with icon\");\n\n    menuA->addSeparator();\n\n    auto menuB = new QMenu(\"Menu B\", this);\n    menuB->addAction(\"Item B1\");\n    menuA->addMenu(menuB);\n\n    menuBar()->addMenu(menuA);\n\n    auto menuC = new QMenu(\"Menu C\", this);\n    connect(menuC, &QMenu::aboutToShow, this, [menuC]() {\n        menuC->clear();\n        menuC->addAction(\"Dynamic Item \" + QDateTime::currentDateTime().toString());\n    });\n\n    menuBar()->addMenu(menuC);\n\n    menuBar()->addAction(\"Top Level Item\");\n}\n\nint main(int argc, char **argv)\n{\n    QApplication app(argc, argv);\n    MainWindow mw;\n    mw.show();\n    return app.exec();\n}\n"
  },
  {
    "path": "src/libdbusmenuqt/utils.cpp",
    "content": "/* This file is part of the dbusmenu-qt library\n   Copyright 2010 Canonical\n   Author: Aurelien Gateau <aurelien.gateau@canonical.com>\n\n   This library is free software; you can redistribute it and/or\n   modify it under the terms of the GNU Library General Public\n   License (LGPL) as published by the Free Software Foundation;\n   either version 2 of the License, or (at your option) any later\n   version.\n\n   This library is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n   Library General Public License for more details.\n\n   You should have received a copy of the GNU Library General Public License\n   along with this library; see the file COPYING.LIB.  If not, write to\n   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n   Boston, MA 02110-1301, USA.\n*/\n#include \"utils_p.h\"\n\n// Qt\n#include <QString>\n\nQString swapMnemonicChar(const QString &in, const char src, const char dst)\n{\n    QString out;\n    bool mnemonicFound = false;\n\n    for (int pos = 0; pos < in.length();) {\n        QChar ch = in[pos];\n        if (ch == src) {\n            if (pos == in.length() - 1) {\n                // 'src' at the end of string, skip it\n                ++pos;\n            } else {\n                if (in[pos + 1] == src) {\n                    // A real 'src'\n                    out += src;\n                    pos += 2;\n                } else if (!mnemonicFound) {\n                    // We found the mnemonic\n                    mnemonicFound = true;\n                    out += dst;\n                    ++pos;\n                } else {\n                    // We already have a mnemonic, just skip the char\n                    ++pos;\n                }\n            }\n        } else if (ch == dst) {\n            // Escape 'dst'\n            out += dst;\n            out += dst;\n            ++pos;\n        } else {\n            out += ch;\n            ++pos;\n        }\n    }\n\n    return out;\n}\n"
  },
  {
    "path": "src/libdbusmenuqt/utils_p.h",
    "content": "/* This file is part of the dbusmenu-qt library\n   Copyright 2010 Canonical\n   Author: Aurelien Gateau <aurelien.gateau@canonical.com>\n\n   This library is free software; you can redistribute it and/or\n   modify it under the terms of the GNU Library General Public\n   License (LGPL) as published by the Free Software Foundation;\n   either version 2 of the License, or (at your option) any later\n   version.\n\n   This library is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n   Library General Public License for more details.\n\n   You should have received a copy of the GNU Library General Public License\n   along with this library; see the file COPYING.LIB.  If not, write to\n   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n   Boston, MA 02110-1301, USA.\n*/\n#ifndef UTILS_P_H\n#define UTILS_P_H\n\nclass QString;\n\n/**\n * Swap mnemonic char: Qt uses '&', while dbusmenu uses '_'\n */\nQString swapMnemonicChar(const QString &in, const char src, const char dst);\n\n#endif /* UTILS_P_H */\n"
  },
  {
    "path": "src/material.json",
    "content": "{\n    \"KPlugin\": {\n        \"Description\": \"Window decoration\",\n        \"EnabledByDefault\": false,\n        \"Id\": \"com.github.zren.material\",\n        \"Name\": \"Material\",\n        \"ServiceTypes\": [\n            \"org.kde.kdecoration2\"\n        ]\n    },\n    \"org.kde.kdecoration2\": {\n        \"blur\": true,\n        \"kcmodule\": true,\n        \"recommendedBorderSize\": \"None\"\n    }\n}\n"
  },
  {
    "path": "src/plugin.cc",
    "content": "/*\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n// own\n#include \"Decoration.h\"\n#include \"Button.h\"\n#include \"ConfigurationModule.h\"\n\n// KF\n#include <KPluginFactory>\n\nK_PLUGIN_FACTORY_WITH_JSON(\n    MaterialDecorationFactory,\n    \"material.json\",\n    registerPlugin<Material::Decoration>();\n    registerPlugin<Material::Button>();\n    registerPlugin<Material::ConfigurationModule>();\n);\n\n#include \"plugin.moc\"\n"
  }
]