Full Code of Zren/material-decoration for AI

master 0e989e5b815b cached
55 files
219.8 KB
55.2k tokens
115 symbols
1 requests
Download .txt
Showing preview only (234K chars total). Download the full file or copy to clipboard to get everything.
Repository: Zren/material-decoration
Branch: master
Commit: 0e989e5b815b
Files: 55
Total size: 219.8 KB

Directory structure:
gitextract_y5m7lijt/

├── .clang-format
├── .gitignore
├── .vscode/
│   └── settings.json
├── CMakeLists.txt
├── LICENSE
├── README.md
└── src/
    ├── AppIconButton.h
    ├── AppMenuButton.cc
    ├── AppMenuButton.h
    ├── AppMenuButtonGroup.cc
    ├── AppMenuButtonGroup.h
    ├── AppMenuModel.cc
    ├── AppMenuModel.h
    ├── ApplicationMenuButton.h
    ├── BoxShadowHelper.cc
    ├── BoxShadowHelper.h
    ├── BuildConfig.h.cmake
    ├── Button.cc
    ├── Button.h
    ├── CMakeLists.txt
    ├── CloseButton.h
    ├── ConfigurationModule.cc
    ├── ConfigurationModule.h
    ├── ContextHelpButton.h
    ├── Decoration.cc
    ├── Decoration.h
    ├── InternalSettings.kcfgc
    ├── InternalSettingsSchema.kcfg
    ├── KeepAboveButton.h
    ├── KeepBelowButton.h
    ├── Material.h
    ├── MaximizeButton.h
    ├── MenuOverflowButton.cc
    ├── MenuOverflowButton.h
    ├── MinimizeButton.h
    ├── OnAllDesktopsButton.h
    ├── ShadeButton.h
    ├── TextButton.cc
    ├── TextButton.h
    ├── libdbusmenuqt/
    │   ├── CMakeLists.txt
    │   ├── README
    │   ├── com.canonical.dbusmenu.xml
    │   ├── dbusmenuimporter.cpp
    │   ├── dbusmenuimporter.h
    │   ├── dbusmenushortcut_p.cpp
    │   ├── dbusmenushortcut_p.h
    │   ├── dbusmenutypes_p.cpp
    │   ├── dbusmenutypes_p.h
    │   ├── test/
    │   │   ├── CMakeLists.txt
    │   │   ├── README
    │   │   └── main.cpp
    │   ├── utils.cpp
    │   └── utils_p.h
    ├── material.json
    └── plugin.cc

================================================
FILE CONTENTS
================================================

================================================
FILE: .clang-format
================================================
---
Language:        Cpp
# BasedOnStyle:  WebKit
AccessModifierOffset: -4
AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands:   false
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
  AfterClass:      false
  AfterControlStatement: false
  AfterEnum:       false
  AfterFunction:   true
  AfterNamespace:  false
  AfterObjCDeclaration: false
  AfterStruct:     false
  AfterUnion:      false
  AfterExternBlock: false
  BeforeCatch:     false
  BeforeElse:      false
  IndentBraces:    false
  SplitEmptyFunction: true
  SplitEmptyRecord: true
  SplitEmptyNamespace: true
BreakBeforeBinaryOperators: All
BreakBeforeBraces: WebKit
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit:     0
CommentPragmas:  '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat:   false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
ForEachMacros:
  - foreach
  - Q_FOREACH
  - BOOST_FOREACH
IncludeBlocks:   Preserve
IncludeCategories:
  - Regex:           '^"(llvm|llvm-c|clang|clang-c)/'
    Priority:        2
  - Regex:           '^(<|"(gtest|gmock|isl|json)/)'
    Priority:        3
  - Regex:           '.*'
    Priority:        1
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false
IndentPPDirectives: None
IndentWidth:     4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd:   ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: Inner
ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Left
RawStringFormats:
  - Delimiter:       pb
    Language:        TextProto
    BasedOnStyle:    google
ReflowComments:  true
SortIncludes:    true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles:  false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard:        Cpp11
TabWidth:        8
UseTab:          Never
...



================================================
FILE: .gitignore
================================================
/build


================================================
FILE: .vscode/settings.json
================================================
{
    "files.associations": {
        "array": "cpp",
        "string_view": "cpp"
    }
}

================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required (VERSION 3.16.0)
project (material-decoration)

add_definitions (-Wall -Werror)

include (FeatureSummary)
find_package (ECM 0.0.9 REQUIRED NO_MODULE)

set (CMAKE_MODULE_PATH
    ${CMAKE_MODULE_PATH}
    ${ECM_MODULE_PATH}
)

include (ECMInstallIcons)
include (KDEInstallDirs)
include (KDECMakeSettings)
include (KDECompilerSettings NO_POLICY_SCOPE)

# set(QT_MIN_VERSION "5.9.0")
# https://doc.qt.io/qt-6/cmake-qt5-and-qt6-compatibility.html#supporting-older-qt-5-versions
# find_package(QT NAMES Qt6 Qt5)
find_package(QT NAMES Qt5)
# find_package(QT NAMES Qt6)
find_package(Qt${QT_VERSION_MAJOR} CONFIG REQUIRED COMPONENTS
    Widgets
    DBus
)
include(ECMQtDeclareLoggingCategory)

# Remove Qt 5.15 Deprecations
# https://doc.qt.io/qt-6/portingguide.html
add_compile_definitions(QT_DISABLE_DEPRECATED_BEFORE=0x050F00)

add_subdirectory (src/libdbusmenuqt)
add_subdirectory (src)

feature_summary(WHAT ALL)


================================================
FILE: LICENSE
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

                    GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) You must cause the modified files to carry prominent notices
    stating that you changed the files and the date of any change.

    b) You must cause any work that you distribute or publish, that in
    whole or in part contains or is derived from the Program or any
    part thereof, to be licensed as a whole at no charge to all third
    parties under the terms of this License.

    c) If the modified program normally reads commands interactively
    when run, you must cause it, when started running for such
    interactive use in the most ordinary way, to print or display an
    announcement including an appropriate copyright notice and a
    notice that there is no warranty (or else, saying that you provide
    a warranty) and that users may redistribute the program under
    these conditions, and telling the user how to view a copy of this
    License.  (Exception: if the Program itself is interactive but
    does not normally print such an announcement, your work based on
    the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

    a) Accompany it with the complete corresponding machine-readable
    source code, which must be distributed under the terms of Sections
    1 and 2 above on a medium customarily used for software interchange; or,

    b) Accompany it with a written offer, valid for at least three
    years, to give any third party, for a charge no more than your
    cost of physically performing source distribution, a complete
    machine-readable copy of the corresponding source code, to be
    distributed under the terms of Sections 1 and 2 above on a medium
    customarily used for software interchange; or,

    c) Accompany it with the information you received as to the offer
    to distribute corresponding source code.  (This alternative is
    allowed only for noncommercial distribution and only if you
    received the program in object code or executable form with such
    an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

                            NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

    Gnomovision version 69, Copyright (C) year name of author
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.


================================================
FILE: README.md
================================================
![Demo](data/preview.png)

## material-decoration

Material-ish window decoration theme for KWin.

### Locally Integrated Menus

This hides the AppMenu icon button and draws the menu in the titlebar.

![](https://i.imgur.com/oFOVWjV.png)

Make sure you add the AppMenu button in System Settings > Application Style > Window Decorations > Buttons Tab.

TODO/Bugs ([Issue #1](https://github.com/Zren/material-decoration/issues/1)):

* Open Submenu on Shortcut (eg: `Alt+F`)
* Display mnemonics when holding `Alt`

Upstream LIM discussion in the KDE Bug report: https://bugs.kde.org/show_bug.cgi?id=375951#c27

### Installation

#### Binary package

- Arch/Manjaro (AUR):  
  Install the `material-kwin-decoration-git` AUR package.  
  https://aur.archlinux.org/packages/material-kwin-decoration-git/

- openSUSE:  
  https://build.opensuse.org/package/show/home:trmdi/material-decoration
```
sudo zypper ar obs://home:trmdi trmdi
sudo zypper in -r trmdi material-decoration
```

#### Building from source
Build dependencies:

- Ubuntu:
```
sudo apt build-dep breeze
sudo apt build-dep kwin
```


Download the source:

```
cd ~/Downloads
git clone https://github.com/Zren/material-decoration.git
cd material-decoration
```

Then compile the decoration, and install it:

```
mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
sudo make install
```

Select Material in System Settings > Application Style > Window Decorations.

To test changes, restart `kwin_x11` with:

```
QT_LOGGING_RULES="*=false;kdecoration.material=true" kstart5 -- kwin_x11 --replace
```

### Update

On 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.

#### Building from source

First navigate to the source directory, and `git pull` recent changes.

```
cd ~/Downloads/material-decoration
git pull origin master --ff-only
```

Then re-run the install instructions.


================================================
FILE: src/AppIconButton.h
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// own
#include "Button.h"
#include "Decoration.h"

// KDecoration
#include <KDecoration2/DecoratedClient>

// KF
#include <KIconLoader>

// Qt
#include <QPainter>
#include <QPalette>

namespace Material
{

class AppIconButton
{

public:
    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {
        QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::iconChanged,
            button, [button] {
                button->update();
            }
        );
    }
    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {
        Q_UNUSED(iconRect)

        const QRectF contentRect = button->contentArea();
        int appIconSize = qMax(16, qRound(gridUnit * 16));
        QRectF appIconRect = QRectF(0, 0, appIconSize, appIconSize);
        appIconRect.moveCenter(contentRect.center().toPoint());

        const auto *deco = qobject_cast<Decoration *>(button->decoration());
        auto *decoratedClient = deco->client().toStrongRef().data();

        const QPalette activePalette = KIconLoader::global()->customPalette();
        QPalette palette = decoratedClient->palette();
        palette.setColor(QPalette::WindowText, deco->titleBarForegroundColor());
        KIconLoader::global()->setCustomPalette(palette);
        decoratedClient->icon().paint(painter, appIconRect.toRect());
        if (activePalette == QPalette()) {
            KIconLoader::global()->resetPalette();
        } else {
            KIconLoader::global()->setCustomPalette(palette);
        }
    }
};

} // namespace Material


================================================
FILE: src/AppMenuButton.cc
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 * Copyright (C) 2016 Kai Uwe Broulik <kde@privat.broulik.de>
 * Copyright (C) 2014 by Hugo Pereira Da Costa <hugo.pereira@free.fr>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

// own
#include "AppMenuButton.h"
#include "Material.h"
#include "Button.h"
#include "Decoration.h"
#include "AppMenuButtonGroup.h"

// KDecoration
#include <KDecoration2/DecoratedClient>

// KF
#include <KColorUtils>

// Qt
#include <QDebug>


namespace Material
{

AppMenuButton::AppMenuButton(Decoration *decoration, const int buttonIndex, QObject *parent)
    : Button(KDecoration2::DecorationButtonType::Custom, decoration, parent)
    , m_buttonIndex(buttonIndex)
{
    setCheckable(true);

    connect(this, &AppMenuButton::clicked,
        this, &AppMenuButton::trigger);

    const auto *buttonGroup = qobject_cast<AppMenuButtonGroup *>(parent);
    if (buttonGroup) {
        setOpacity(buttonGroup->opacity());
    }
}

AppMenuButton::~AppMenuButton()
{
}

int AppMenuButton::buttonIndex() const
{
    return m_buttonIndex;
}

QColor AppMenuButton::backgroundColor() const
{
    const auto *buttonGroup = qobject_cast<AppMenuButtonGroup *>(parent());
    if (buttonGroup
        && buttonGroup->isMenuOpen()
        && buttonGroup->currentIndex() != m_buttonIndex
    ) {
        return Qt::transparent;
    } else {
        return Button::backgroundColor();
    }
}

QColor AppMenuButton::foregroundColor() const
{
    const auto *buttonGroup = qobject_cast<AppMenuButtonGroup *>(parent());
    if (buttonGroup
        && buttonGroup->isMenuOpen()
        && buttonGroup->currentIndex() != m_buttonIndex
    ) {
        const auto *deco = qobject_cast<Decoration *>(decoration());
        if (!deco) {
            return {};
        }
        return KColorUtils::mix(
            deco->titleBarBackgroundColor(),
            deco->titleBarForegroundColor(),
            0.8);
    } else {
        return Button::foregroundColor();
    }
}

void AppMenuButton::trigger() {
    // qCDebug(category) << "AppMenuButton::trigger" << m_buttonIndex;

    auto *buttonGroup = qobject_cast<AppMenuButtonGroup *>(parent());
    buttonGroup->trigger(m_buttonIndex);
}

} // namespace Material


================================================
FILE: src/AppMenuButton.h
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// own
#include "Button.h"

namespace Material
{

class Decoration;

class AppMenuButton : public Button
{
    Q_OBJECT

public:
    AppMenuButton(Decoration *decoration, const int buttonIndex, QObject *parent = nullptr);
    ~AppMenuButton() override;

    Q_PROPERTY(int buttonIndex READ buttonIndex NOTIFY buttonIndexChanged)

    int buttonIndex() const;

    QColor backgroundColor() const override;
    QColor foregroundColor() const override;

signals:
    void buttonIndexChanged();

public slots:
    virtual void trigger();

private:
    int m_buttonIndex;
};

} // namespace Material


================================================
FILE: src/AppMenuButtonGroup.cc
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 * Copyright (C) 2016 Kai Uwe Broulik <kde@privat.broulik.de>
 * Copyright (C) 2014 by Hugo Pereira Da Costa <hugo.pereira@free.fr>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

// own
#include "AppMenuButtonGroup.h"
#include "Material.h"
#include "BuildConfig.h"
#include "AppMenuModel.h"
#include "Decoration.h"
#include "AppMenuButton.h"
#include "TextButton.h"
#include "MenuOverflowButton.h"

// KDecoration
#include <KDecoration2/DecoratedClient>
#include <KDecoration2/DecorationButton>
#include <KDecoration2/DecorationButtonGroup>

// KF
#include <KWindowSystem>

// Qt
#include <QAction>
#include <QDebug>
#include <QMenu>
#include <QPainter>
#include <QVariantAnimation>


namespace Material
{

AppMenuButtonGroup::AppMenuButtonGroup(Decoration *decoration)
    : KDecoration2::DecorationButtonGroup(decoration)
    , m_appMenuModel(nullptr)
    , m_currentIndex(-1)
    , m_overflowIndex(-1)
    , m_hovered(false)
    , m_showing(true)
    , m_alwaysShow(true)
    , m_animationEnabled(false)
    , m_animation(new QVariantAnimation(this))
    , m_opacity(1)
{
    // Assign showing and opacity before we bind the onShowingChanged animation
    // so that new windows do not animate.
    setAlwaysShow(decoration->menuAlwaysShow());
    updateShowing();
    setOpacity(m_showing ? 1 : 0);

    connect(this, &AppMenuButtonGroup::showingChanged,
            this, &AppMenuButtonGroup::onShowingChanged);
    connect(this, &AppMenuButtonGroup::hoveredChanged,
            this, &AppMenuButtonGroup::updateShowing);
    connect(this, &AppMenuButtonGroup::alwaysShowChanged,
            this, &AppMenuButtonGroup::updateShowing);
    connect(this, &AppMenuButtonGroup::currentIndexChanged,
            this, &AppMenuButtonGroup::updateShowing);

    m_animationEnabled = decoration->animationsEnabled();
    m_animation->setDuration(decoration->animationsDuration());
    m_animation->setStartValue(0.0);
    m_animation->setEndValue(1.0);
    m_animation->setEasingCurve(QEasingCurve::InOutQuad);
    connect(m_animation, &QVariantAnimation::valueChanged, this, [this](const QVariant &value) {
        setOpacity(value.toReal());
    });
    connect(this, &AppMenuButtonGroup::opacityChanged, this, [this]() {
        // update();
    });

    auto *decoratedClient = decoration->client().toStrongRef().data();
    connect(decoratedClient, &KDecoration2::DecoratedClient::hasApplicationMenuChanged,
            this, &AppMenuButtonGroup::updateAppMenuModel);
    connect(this, &AppMenuButtonGroup::requestActivateIndex,
            this, &AppMenuButtonGroup::trigger);
    connect(this, &AppMenuButtonGroup::requestActivateOverflow,
            this, &AppMenuButtonGroup::triggerOverflow);
}

AppMenuButtonGroup::~AppMenuButtonGroup()
{
}

int AppMenuButtonGroup::currentIndex() const
{
    return m_currentIndex;
}

void AppMenuButtonGroup::setCurrentIndex(int set)
{
    if (m_currentIndex != set) {
        m_currentIndex = set;
        // qCDebug(category) << this << "setCurrentIndex" << m_currentIndex;
        emit currentIndexChanged();
    }
}

bool AppMenuButtonGroup::overflowing() const
{
    return m_overflowing;
}

void AppMenuButtonGroup::setOverflowing(bool set)
{
    if (m_overflowing != set) {
        m_overflowing = set;
        // qCDebug(category) << this << "setOverflowing" << m_overflowing;
        emit overflowingChanged();
    }
}

bool AppMenuButtonGroup::hovered() const
{
    return m_hovered;
}

void AppMenuButtonGroup::setHovered(bool value)
{
    if (m_hovered != value) {
        m_hovered = value;
        // qCDebug(category) << this << "setHovered" << m_hovered;
        emit hoveredChanged(value);
    }
}

bool AppMenuButtonGroup::showing() const
{
    return m_showing;
}

void AppMenuButtonGroup::setShowing(bool value)
{
    if (m_showing != value) {
        m_showing = value;
        // qCDebug(category) << this << "setShowing" << m_showing << "alwaysShow" << m_alwaysShow << "currentIndex" << m_currentIndex << "opacity" << m_opacity;
        emit showingChanged(value);
    }
}

bool AppMenuButtonGroup::alwaysShow() const
{
    return m_alwaysShow;
}

void AppMenuButtonGroup::setAlwaysShow(bool value)
{
    if (m_alwaysShow != value) {
        m_alwaysShow = value;
        // qCDebug(category) << this << "setAlwaysShow" << m_alwaysShow;
        emit alwaysShowChanged(value);
    }
}

bool AppMenuButtonGroup::animationEnabled() const
{
    return m_animationEnabled;
}

void AppMenuButtonGroup::setAnimationEnabled(bool value)
{
    if (m_animationEnabled != value) {
        m_animationEnabled = value;
        emit animationEnabledChanged(value);
    }
}

int AppMenuButtonGroup::animationDuration() const
{
    return m_animation->duration();
}

void AppMenuButtonGroup::setAnimationDuration(int value)
{
    if (m_animation->duration() != value) {
        m_animation->setDuration(value);
        emit animationDurationChanged(value);
    }
}

qreal AppMenuButtonGroup::opacity() const
{
    return m_opacity;
}

void AppMenuButtonGroup::setOpacity(qreal value)
{
    if (m_opacity != value) {
        m_opacity = value;

        for (int i = 0; i < buttons().length(); i++) {
            KDecoration2::DecorationButton* decoButton = buttons().value(i);
            auto *button = qobject_cast<Button *>(decoButton);
            if (button) {
                button->setOpacity(m_opacity);
            }
        }

        emit opacityChanged(value);
    }
}

KDecoration2::DecorationButton* AppMenuButtonGroup::buttonAt(int x, int y) const
{
    for (int i = 0; i < buttons().length(); i++) {
        KDecoration2::DecorationButton* button = buttons().value(i);
        if (!button->isVisible()) {
            continue;
        }
        if (button->geometry().contains(x, y)) {
            return button;
        }
    }
    return nullptr;
}

void AppMenuButtonGroup::resetButtons()
{
    // qCDebug(category) << "    resetButtons";
    // qCDebug(category) << "        before" << buttons();
    auto list = QVector<QPointer<KDecoration2::DecorationButton>>(buttons());
    // qCDebug(category) << "          list" << list;
    removeButton(KDecoration2::DecorationButtonType::Custom);
    // qCDebug(category) << "     remCustom" << buttons();
    while (!list.isEmpty()) {
        auto item = list.takeFirst();
        // qCDebug(category) << "        delete" << item;
        delete item;
    }
    // qCDebug(category) << "         after" << list;
    emit menuUpdated();
}

void AppMenuButtonGroup::initAppMenuModel()
{
    m_appMenuModel = new AppMenuModel(this);
    connect(m_appMenuModel, &AppMenuModel::modelReset,
        this, &AppMenuButtonGroup::updateAppMenuModel);
    // qCDebug(category) << "AppMenuModel" << m_appMenuModel;
}

void AppMenuButtonGroup::updateAppMenuModel()
{
    auto *deco = qobject_cast<Decoration *>(decoration());
    if (!deco) {
        return;
    }
    auto *decoratedClient = deco->client().toStrongRef().data();

    // Don't display AppMenu in modal windows.
    if (decoratedClient->isModal()) {
        resetButtons();
        return;
    }

    if (!decoratedClient->hasApplicationMenu()) {
        resetButtons();
        return;
    }

    if (m_appMenuModel) {
        // Update AppMenuModel
        // qCDebug(category) << "AppMenuModel" << m_appMenuModel;

        resetButtons();

        // Populate
        for (int row = 0; row < m_appMenuModel->rowCount(); row++) {
            const QModelIndex index = m_appMenuModel->index(row, 0);
            const QString itemLabel = m_appMenuModel->data(index, AppMenuModel::MenuRole).toString();

            // https://github.com/psifidotos/applet-window-appmenu/blob/908e60831d7d68ee56a56f9c24017a71822fc02d/lib/appmenuapplet.cpp#L167
            const QVariant data = m_appMenuModel->data(index, AppMenuModel::ActionRole);
            QAction *itemAction = (QAction *)data.value<void *>();

            // qCDebug(category) << "    " << itemAction;

            TextButton *b = new TextButton(deco, row, this);
            b->setText(itemLabel);
            b->setAction(itemAction);
            b->setOpacity(m_opacity);

            // Skip items with empty labels (The first item in a Gtk app)
            if (itemLabel.isEmpty()) {
                b->setEnabled(false);
                b->setVisible(false);
            }
            
            addButton(QPointer<KDecoration2::DecorationButton>(b));
        }
        m_overflowIndex = m_appMenuModel->rowCount();
        addButton(new MenuOverflowButton(deco, m_overflowIndex, this));

        emit menuUpdated();

    } else {
        // Init AppMenuModel
        // qCDebug(category) << "windowId" << decoratedClient->windowId();
        if (KWindowSystem::isPlatformX11()) {
#if HAVE_X11
            WId windowId = decoratedClient->windowId();
            if (windowId != 0) {
                initAppMenuModel();
                m_appMenuModel->setWinId(windowId);
                // qCDebug(category) << "AppMenuModel" << m_appMenuModel;
            }
#endif
        } else if (KWindowSystem::isPlatformWayland()) {
#if HAVE_Wayland
            // TODO
#endif
        }
    }
}

void AppMenuButtonGroup::updateOverflow(QRectF availableRect)
{
    // qCDebug(category) << "updateOverflow" << availableRect;
    bool showOverflow = false;
    for (KDecoration2::DecorationButton *button : buttons()) {
        // qCDebug(category) << "    " << button->geometry() << button;
        if (qobject_cast<MenuOverflowButton *>(button)) {
            button->setVisible(showOverflow);
            // qCDebug(category) << "    showOverflow" << showOverflow;
        } else if (qobject_cast<TextButton *>(button)) {
            if (button->isEnabled()) {
                if (availableRect.contains(button->geometry())) {
                    button->setVisible(true);
                } else {
                    button->setVisible(false);
                    showOverflow = true;
                }
            }
        }
    }
    setOverflowing(showOverflow);
}

void AppMenuButtonGroup::trigger(int buttonIndex) {
    // qCDebug(category) << "AppMenuButtonGroup::trigger" << buttonIndex;
    KDecoration2::DecorationButton* button = buttons().value(buttonIndex);

    // https://github.com/psifidotos/applet-window-appmenu/blob/908e60831d7d68ee56a56f9c24017a71822fc02d/lib/appmenuapplet.cpp#L167
    QMenu *actionMenu = nullptr;

    if (buttonIndex == m_appMenuModel->rowCount()) {
        // Overflow Menu
        actionMenu = new QMenu();
        actionMenu->setAttribute(Qt::WA_DeleteOnClose);

        int overflowStartsAt = 0;
        for (KDecoration2::DecorationButton *b : buttons()) {
            TextButton* textButton = qobject_cast<TextButton *>(b);
            if (textButton && textButton->isEnabled() && !textButton->isVisible()) {
                overflowStartsAt = textButton->buttonIndex();
                break;
            }
        }

        QAction *action = nullptr;
        for (int i = overflowStartsAt; i < m_appMenuModel->rowCount(); i++) {
            const QModelIndex index = m_appMenuModel->index(i, 0);
            const QVariant data = m_appMenuModel->data(index, AppMenuModel::ActionRole);
            action = (QAction *)data.value<void *>();
            actionMenu->addAction(action);
        }

    } else {
        const QModelIndex modelIndex = m_appMenuModel->index(buttonIndex, 0);
        const QVariant data = m_appMenuModel->data(modelIndex, AppMenuModel::ActionRole);
        QAction *itemAction = (QAction *)data.value<void *>();
        // qCDebug(category) << "    action" << itemAction;

        if (itemAction) {
            actionMenu = itemAction->menu();
            // qCDebug(category) << "    menu" << actionMenu;
        }
    }

    const auto *deco = qobject_cast<Decoration *>(decoration());
    // if (actionMenu && deco) {
    //     auto *decoratedClient = deco->client().toStrongRef().data();
    //     actionMenu->setPalette(decoratedClient->palette());
    // }

    if (actionMenu && deco) {
        QRectF buttonRect = button->geometry();
        QPoint position = buttonRect.topLeft().toPoint();
        QPoint rootPosition(position);
        rootPosition += deco->windowPos();
        // qCDebug(category) << "    windowPos" << windowPos;

        // auto connection( QX11Info::connection() );

        // button release event
        // xcb_button_release_event_t releaseEvent;
        // memset(&releaseEvent, 0, sizeof(releaseEvent));

        // releaseEvent.response_type = XCB_BUTTON_RELEASE;
        // releaseEvent.event =  windowId;
        // releaseEvent.child = XCB_WINDOW_NONE;
        // releaseEvent.root = QX11Info::appRootWindow();
        // releaseEvent.event_x = position.x();
        // releaseEvent.event_y = position.y();
        // releaseEvent.root_x = rootPosition.x();
        // releaseEvent.root_y = rootPosition.y();
        // releaseEvent.detail = XCB_BUTTON_INDEX_1;
        // releaseEvent.state = XCB_BUTTON_MASK_1;
        // releaseEvent.time = XCB_CURRENT_TIME;
        // releaseEvent.same_screen = true;
        // xcb_send_event( connection, false, windowId, XCB_EVENT_MASK_BUTTON_RELEASE, reinterpret_cast<const char*>(&releaseEvent));

        // xcb_ungrab_pointer( connection, XCB_TIME_CURRENT_TIME );
        //---

        actionMenu->installEventFilter(this);

        if (!KWindowSystem::isPlatformWayland()) {
            actionMenu->popup(rootPosition);
        }

        QMenu *oldMenu = m_currentMenu;
        m_currentMenu = actionMenu;

        if (oldMenu && oldMenu != actionMenu) {
            // Don't reset the currentIndex when another menu is already shown
            disconnect(oldMenu, &QMenu::aboutToHide, this, &AppMenuButtonGroup::onMenuAboutToHide);
            oldMenu->hide();
        }
        if (0 <= m_currentIndex && m_currentIndex < buttons().length()) {
            buttons().value(m_currentIndex)->setChecked(false);
        }

        if (KWindowSystem::isPlatformWayland()) {
            actionMenu->popup(rootPosition);
        }

        setCurrentIndex(buttonIndex);
        button->setChecked(true);

        // FIXME TODO connect only once
        connect(actionMenu, &QMenu::aboutToHide, this, &AppMenuButtonGroup::onMenuAboutToHide, Qt::UniqueConnection);
    }
}

void AppMenuButtonGroup::triggerOverflow()
{
    // qCDebug(category) << "AppMenuButtonGroup::triggerOverflow" << m_overflowIndex;
    trigger(m_overflowIndex);
}

// FIXME TODO doesn't work on submenu
bool AppMenuButtonGroup::eventFilter(QObject *watched, QEvent *event)
{
    auto *menu = qobject_cast<QMenu *>(watched);

    if (!menu) {
        return false;
    }

    if (event->type() == QEvent::KeyPress) {
        auto *e = static_cast<QKeyEvent *>(event);

        // TODO right to left languages
        if (e->key() == Qt::Key_Left) {
            int desiredIndex = m_currentIndex - 1;
            emit requestActivateIndex(desiredIndex);
            return true;
        } else if (e->key() == Qt::Key_Right) {
            if (menu->activeAction() && menu->activeAction()->menu()) {
                return false;
            }

            int desiredIndex = m_currentIndex + 1;
            emit requestActivateIndex(desiredIndex);
            return true;
        }

    } else if (event->type() == QEvent::MouseMove) {
        auto *e = static_cast<QMouseEvent *>(event);

        const auto *deco = qobject_cast<Decoration *>(decoration());

        QPoint decoPos(e->globalPos());
        decoPos -= deco->windowPos();
        decoPos.ry() += deco->titleBarHeight();
        // qCDebug(category) << "MouseMove";
        // qCDebug(category) << "       globalPos" << e->globalPos();
        // qCDebug(category) << "       windowPos" << deco->windowPos();
        // qCDebug(category) << "  titleBarHeight" << deco->titleBarHeight();

        KDecoration2::DecorationButton* item = buttonAt(decoPos.x(), decoPos.y());
        if (!item) {
            return false;
        }

        AppMenuButton* appMenuButton = qobject_cast<AppMenuButton *>(item);
        if (appMenuButton) {
            if (m_currentIndex != appMenuButton->buttonIndex()
                && appMenuButton->isVisible()
                && appMenuButton->isEnabled()
            ) {
                emit requestActivateIndex(appMenuButton->buttonIndex());
            }
            return false;
        }
    }

    return false;
}

bool AppMenuButtonGroup::isMenuOpen() const
{
    return 0 <= m_currentIndex;
}

void AppMenuButtonGroup::unPressAllButtons()
{
    // qCDebug(category) << "AppMenuButtonGroup::unPressAllButtons";
    for (int i = 0; i < buttons().length(); i++) {
        KDecoration2::DecorationButton* button = buttons().value(i);

        // Hack to setPressed(false)
        button->setEnabled(!button->isEnabled());
        button->setEnabled(!button->isEnabled());
    }
}

void AppMenuButtonGroup::updateShowing()
{
    setShowing(m_alwaysShow || m_hovered || isMenuOpen());
}

void AppMenuButtonGroup::onMenuAboutToHide()
{
    if (0 <= m_currentIndex && m_currentIndex < buttons().length()) {
        buttons().value(m_currentIndex)->setChecked(false);
    }
    setCurrentIndex(-1);
}

void AppMenuButtonGroup::onShowingChanged(bool showing)
{
    if (m_animationEnabled) {
        QAbstractAnimation::Direction dir = showing ? QAbstractAnimation::Forward : QAbstractAnimation::Backward;
        if (m_animation->state() == QAbstractAnimation::Running && m_animation->direction() != dir) {
            m_animation->stop();
        }
        m_animation->setDirection(dir);
        if (m_animation->state() != QAbstractAnimation::Running) {
            m_animation->start();
        }
    } else {
        setOpacity(showing ? 1 : 0);
    }
}

} // namespace Material


================================================
FILE: src/AppMenuButtonGroup.h
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// own
#include "AppMenuModel.h"

// KDecoration
#include <KDecoration2/DecoratedClient>
#include <KDecoration2/DecorationButton>
#include <KDecoration2/DecorationButtonGroup>

// Qt
#include <QMenu>
#include <QVariantAnimation>

namespace Material
{

class Decoration;

class AppMenuButtonGroup : public KDecoration2::DecorationButtonGroup
{
    Q_OBJECT

public:
    AppMenuButtonGroup(Decoration *decoration);
    ~AppMenuButtonGroup() override;

    Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
    Q_PROPERTY(int overflowing READ overflowing WRITE setOverflowing NOTIFY overflowingChanged)
    Q_PROPERTY(bool hovered READ hovered WRITE setHovered NOTIFY hoveredChanged)
    Q_PROPERTY(bool showing READ showing WRITE setShowing NOTIFY showingChanged)
    Q_PROPERTY(bool alwaysShow READ alwaysShow WRITE setAlwaysShow NOTIFY alwaysShowChanged)
    Q_PROPERTY(bool animationEnabled READ animationEnabled WRITE setAnimationEnabled NOTIFY animationEnabledChanged)
    Q_PROPERTY(int animationDuration READ animationDuration WRITE setAnimationDuration NOTIFY animationDurationChanged)
    Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged)

    int currentIndex() const;
    void setCurrentIndex(int set);

    bool overflowing() const;
    void setOverflowing(bool set);

    bool hovered() const;
    void setHovered(bool value);

    bool showing() const;
    void setShowing(bool value);

    bool alwaysShow() const;
    void setAlwaysShow(bool value);

    bool animationEnabled() const;
    void setAnimationEnabled(bool value);

    int animationDuration() const;
    void setAnimationDuration(int duration);

    qreal opacity() const;
    void setOpacity(qreal value);

    bool isMenuOpen() const;

    KDecoration2::DecorationButton* buttonAt(int x, int y) const;

    void unPressAllButtons();

public slots:
    void initAppMenuModel();
    void updateAppMenuModel();
    void updateOverflow(QRectF availableRect);
    void trigger(int index);
    void triggerOverflow();
    void updateShowing();
    void onMenuAboutToHide();

private slots:
    void onShowingChanged(bool hovered);

signals:
    void menuUpdated();
    void requestActivateIndex(int index);
    void requestActivateOverflow();

    void currentIndexChanged();
    void overflowingChanged();
    void hoveredChanged(bool);
    void showingChanged(bool);
    void alwaysShowChanged(bool);
    void animationEnabledChanged(bool);
    void animationDurationChanged(int);
    void opacityChanged(qreal);

protected:
    bool eventFilter(QObject *watched, QEvent *event) override;

private:
    void resetButtons();

    AppMenuModel *m_appMenuModel;
    int m_currentIndex;
    int m_overflowIndex;
    bool m_overflowing;
    bool m_hovered;
    bool m_showing;
    bool m_alwaysShow;
    bool m_animationEnabled;
    QVariantAnimation *m_animation;
    qreal m_opacity;
    QPointer<QMenu> m_currentMenu;
};

} // namespace Material


================================================
FILE: src/AppMenuModel.cc
================================================
/******************************************************************
 * Copyright 2016 Kai Uwe Broulik <kde@privat.broulik.de>
 * Copyright 2016 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License or (at your option) version 3 or any later version
 * accepted by the membership of KDE e.V. (or its successor approved
 * by the membership of KDE e.V.), which shall act as a proxy
 * defined in Section 14 of version 3 of the license.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 ******************************************************************/

// Based on:
// https://invent.kde.org/plasma/plasma-workspace/-/blob/master/applets/appmenu/plugin/appmenumodel.cpp
// https://github.com/psifidotos/applet-window-appmenu/blob/master/plugin/appmenumodel.cpp

// own
#include "AppMenuModel.h"
#include "Material.h"
#include "BuildConfig.h"

// KF
#include <KWindowSystem>
// In KF5 5.101, KWindowSystem moved several signals to KX11Extras
// Eg: https://invent.kde.org/frameworks/kwindowsystem/-/commit/7cfd7c36eb017242d7a0202db82895be6b8fb81c
#if HAVE_KF5_101 // KX11Extras
#include <KX11Extras>
#endif

// Qt
#include <QAction>
#include <QDebug>
#include <QMenu>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusServiceWatcher>
#include <QGuiApplication>

// libdbusmenuqt
#include <dbusmenuimporter.h>

#if HAVE_X11
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <private/qtx11extras_p.h>
#else
#include <QX11Info>
#endif
#include <xcb/xcb.h>
#endif


namespace Material
{

static const QByteArray s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME");
static const QByteArray s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH");

#if HAVE_X11
static QHash<QByteArray, xcb_atom_t> s_atoms;
#endif

class KDBusMenuImporter : public DBusMenuImporter
{

public:
    KDBusMenuImporter(const QString &service, const QString &path, QObject *parent)
        : DBusMenuImporter(service, path, parent) {

    }

protected:
    QIcon iconForName(const QString &name) override {
        return QIcon::fromTheme(name);
    }

};

AppMenuModel::AppMenuModel(QObject *parent)
    : QAbstractListModel(parent),
      m_serviceWatcher(new QDBusServiceWatcher(this))
{
    if (KWindowSystem::isPlatformX11()) {
#if HAVE_X11
        x11Init();
#else
        // Not compiled with X11
        return;
#endif

    } else if (KWindowSystem::isPlatformWayland()) {
#if HAVE_Wayland
        // TODO
        // waylandInit();
        return;
#else
        // Not compiled with KWayland
        return;
#endif

    } else {
        // Not X11 or Wayland
        return;
    }

    m_serviceWatcher->setConnection(QDBusConnection::sessionBus());
    // If our current DBus connection gets lost, close the menu
    // we'll select the new menu when the focus changes
    connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString & serviceName) {
        if (serviceName == m_serviceName) {
            setMenuAvailable(false);
            emit modelNeedsUpdate();
        }
    });
}

AppMenuModel::~AppMenuModel() = default;

void AppMenuModel::x11Init()
{
#if HAVE_X11
    connect(this, &AppMenuModel::winIdChanged,
            this, &AppMenuModel::onWinIdChanged);

// In KF5 5.101, KWindowSystem moved several signals to KX11Extras
// Eg: https://invent.kde.org/frameworks/kwindowsystem/-/commit/7cfd7c36eb017242d7a0202db82895be6b8fb81c
#if HAVE_KF5_101 // KX11Extras
    // Select non-deprecated overloaded method. Uses coding pattern from:
    // https://invent.kde.org/plasma/plasma-workspace/blame/master/libtaskmanager/xwindowsystemeventbatcher.cpp#L30
    void (KX11Extras::*myWindowChangeSignal)(WId window, NET::Properties properties, NET::Properties2 properties2) = &KX11Extras::windowChanged;
    connect(KX11Extras::self(), myWindowChangeSignal,
            this, &AppMenuModel::onX11WindowChanged);

    // There are apps that are not releasing their menu properly after closing
    // and as such their menu is still shown even though the app does not exist
    // any more. Such apps are Java based e.g. smartgit
    connect(KX11Extras::self(), &KX11Extras::windowRemoved,
            this, &AppMenuModel::onX11WindowRemoved);
#else // KF5 5.100 KWindowSystem
    void (KWindowSystem::*myWindowChangeSignal)(WId window, NET::Properties properties, NET::Properties2 properties2) = &KWindowSystem::windowChanged;
    connect(KWindowSystem::self(), myWindowChangeSignal,
            this, &AppMenuModel::onX11WindowChanged);
    connect(KWindowSystem::self(), &KWindowSystem::windowRemoved,
            this, &AppMenuModel::onX11WindowRemoved);
#endif

    connect(this, &AppMenuModel::modelNeedsUpdate, this, [this] {
        if (!m_updatePending) {
            m_updatePending = true;
            QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
        }
    });
#endif
}

void AppMenuModel::waylandInit()
{
#if HAVE_Wayland
    // TODO
#endif
}

bool AppMenuModel::menuAvailable() const
{
    return m_menuAvailable;
}

void AppMenuModel::setMenuAvailable(bool set)
{
    if (m_menuAvailable != set) {
        m_menuAvailable = set;
        emit menuAvailableChanged();
    }
}

QVariant AppMenuModel::winId() const
{
    return m_winId;
}

void AppMenuModel::setWinId(const QVariant &id)
{
    if (m_winId == id) {
        return;
    }
    qCDebug(category) << "AppMenuModel::setWinId" << m_winId << " => " << id;
    m_winId = id;
    emit winIdChanged();
}

int AppMenuModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);

    if (!m_menuAvailable || !m_menu) {
        return 0;
    }

    return m_menu->actions().count();
}

void AppMenuModel::update()
{
    // qCDebug(category) << "AppMenuModel::update (" << m_winId << ")";
    beginResetModel();
    endResetModel();
    m_updatePending = false;
}


void AppMenuModel::onWinIdChanged()
{

    if (KWindowSystem::isPlatformX11()) {
#if HAVE_X11

        qApp->removeNativeEventFilter(this);

        const WId id = m_winId.toUInt();

        if (!id) {
            setMenuAvailable(false);
            emit modelNeedsUpdate();
            return;
        }

        auto *c = QX11Info::connection();

        auto getWindowPropertyString = [c](WId id, const QByteArray &name) -> QByteArray {
            QByteArray value;

            if (!s_atoms.contains(name))
            {
                const xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom(c, false, name.length(), name.constData());
                QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> atomReply(xcb_intern_atom_reply(c, atomCookie, nullptr));

                if (atomReply.isNull()) {
                    return value;
                }

                s_atoms[name] = atomReply->atom;

                if (s_atoms[name] == XCB_ATOM_NONE) {
                    return value;
                }
            }

            static const long MAX_PROP_SIZE = 10000;
            auto propertyCookie = xcb_get_property(c, false, id, s_atoms[name], XCB_ATOM_STRING, 0, MAX_PROP_SIZE);
            QScopedPointer<xcb_get_property_reply_t, QScopedPointerPodDeleter> propertyReply(xcb_get_property_reply(c, propertyCookie, nullptr));

            if (propertyReply.isNull())
            {
                return value;
            }

            if (propertyReply->type == XCB_ATOM_STRING && propertyReply->format == 8 && propertyReply->value_len > 0)
            {
                const char *data = (const char *) xcb_get_property_value(propertyReply.data());
                int len = propertyReply->value_len;

                if (data) {
                    value = QByteArray(data, data[len - 1] ? len : len - 1);
                }
            }

            return value;
        };

        auto updateMenuFromWindowIfHasMenu = [this, &getWindowPropertyString](WId id) {
            const QString serviceName = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuServiceNamePropertyName));
            const QString menuObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuObjectPathPropertyName));

            if (!serviceName.isEmpty() && !menuObjectPath.isEmpty()) {
                updateApplicationMenu(serviceName, menuObjectPath);
                return true;
            }

            return false;
        };

        if (updateMenuFromWindowIfHasMenu(id)) {
            return;
        }

        // monitor whether an app menu becomes available later
        // this can happen when an app starts, shows its window, and only later announces global menu (e.g. Firefox)
        qApp->installNativeEventFilter(this);
        m_delayedMenuWindowId = id;

        //no menu found, set it to unavailable
        setMenuAvailable(false);
        emit modelNeedsUpdate();
#endif

    } else if (KWindowSystem::isPlatformWayland()) {
#if HAVE_Wayland
        // TODO
#endif
    }
}

void AppMenuModel::onX11WindowChanged(WId id)
{
    if (m_winId.toUInt() == id) {
        
    }
}

void AppMenuModel::onX11WindowRemoved(WId id)
{
    if (m_winId.toUInt() == id) {
        setMenuAvailable(false);
    }
}

QHash<int, QByteArray> AppMenuModel::roleNames() const
{
    QHash<int, QByteArray> roleNames;
    roleNames[MenuRole] = QByteArrayLiteral("activeMenu");
    roleNames[ActionRole] = QByteArrayLiteral("activeActions");
    return roleNames;
}

QVariant AppMenuModel::data(const QModelIndex &index, int role) const
{
    const int row = index.row();

    if (row < 0 || !m_menuAvailable || !m_menu) {
        return QVariant();
    }

    const auto actions = m_menu->actions();

    if (row >= actions.count()) {
        return QVariant();
    }

    if (role == MenuRole) { // TODO this should be Qt::DisplayRole
        return actions.at(row)->text();
    } else if (role == ActionRole) {
        return QVariant::fromValue((void *) actions.at(row));
    }

    return QVariant();
}

void AppMenuModel::updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath)
{
    if (m_serviceName == serviceName && m_menuObjectPath == menuObjectPath) {
        if (m_importer) {
            QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection);
        }
        return;
    }

    m_serviceName = serviceName;
    m_serviceWatcher->setWatchedServices(QStringList({m_serviceName}));

    m_menuObjectPath = menuObjectPath;

    if (m_importer) {
        m_importer->deleteLater();
    }

    m_importer = new KDBusMenuImporter(serviceName, menuObjectPath, this);
    QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection);

    connect(m_importer.data(), &DBusMenuImporter::menuUpdated, this, [=](QMenu *menu) {
        m_menu = m_importer->menu();
        if (m_menu.isNull() || menu != m_menu) {
            return;
        }

        // cache first layer of sub menus, which we'll be popping up
        const auto actions = m_menu->actions();
        for (QAction *a : actions) {
            // signal dataChanged when the action changes
            connect(a, &QAction::changed, this, [this, a] {
                if (m_menuAvailable && m_menu) {
                    const int actionIdx = m_menu->actions().indexOf(a);
                    if (actionIdx > -1) {
                        const QModelIndex modelIdx = index(actionIdx, 0);
                        emit dataChanged(modelIdx, modelIdx);
                    }
                }
            });

            connect(a, &QAction::destroyed, this, &AppMenuModel::modelNeedsUpdate);

            if (a->menu()) {
                m_importer->updateMenu(a->menu());
            }
        }

        setMenuAvailable(true);
        emit modelNeedsUpdate();
    });

    connect(m_importer.data(), &DBusMenuImporter::actionActivationRequested, this, [this](QAction *action) {
        // TODO submenus
        if (!m_menuAvailable || !m_menu) {
            return;
        }

        const auto actions = m_menu->actions();
        auto it = std::find(actions.begin(), actions.end(), action);
        if (it != actions.end()) {
            emit requestActivateIndex(it - actions.begin());
        }
    });
}

bool AppMenuModel::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
    Q_UNUSED(result);

    if (!KWindowSystem::isPlatformX11() || eventType != "xcb_generic_event_t") {
        return false;
    }

#if HAVE_X11
    auto e = static_cast<xcb_generic_event_t *>(message);
    const uint8_t type = e->response_type & ~0x80;

    if (type == XCB_PROPERTY_NOTIFY) {
        auto *event = reinterpret_cast<xcb_property_notify_event_t *>(e);

        if (event->window == m_delayedMenuWindowId) {

            auto serviceNameAtom = s_atoms.value(s_x11AppMenuServiceNamePropertyName);
            auto objectPathAtom = s_atoms.value(s_x11AppMenuObjectPathPropertyName);

            if (serviceNameAtom != XCB_ATOM_NONE && objectPathAtom != XCB_ATOM_NONE) { // shouldn't happen
                if (event->atom == serviceNameAtom || event->atom == objectPathAtom) {
                    // see if we now have a menu
                    onWinIdChanged();
                }
            }
        }
    }

#else
    Q_UNUSED(message);
#endif

    return false;
}

} // namespace Material


================================================
FILE: src/AppMenuModel.h
================================================
/******************************************************************
 * Copyright 2016 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License or (at your option) version 3 or any later version
 * accepted by the membership of KDE e.V. (or its successor approved
 * by the membership of KDE e.V.), which shall act as a proxy
 * defined in Section 14 of version 3 of the license.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 ******************************************************************/

#pragma once

// Qt
#include <QAbstractListModel>
#include <QAbstractNativeEventFilter>
#include <QAction>
#include <QDBusServiceWatcher>
#include <QMenu>
#include <QModelIndex>
#include <QPointer>
#include <QRect>
#include <QStringList>


namespace Material
{

class KDBusMenuImporter;

class AppMenuModel : public QAbstractListModel, public QAbstractNativeEventFilter
{
    Q_OBJECT

    Q_PROPERTY(bool menuAvailable READ menuAvailable WRITE setMenuAvailable NOTIFY menuAvailableChanged)
    Q_PROPERTY(QVariant winId READ winId WRITE setWinId NOTIFY winIdChanged)

public:
    explicit AppMenuModel(QObject *parent = nullptr);
    ~AppMenuModel() override;

private:
    void x11Init();
    void waylandInit();

public:
    enum AppMenuRole
    {
        MenuRole = Qt::UserRole + 1, // TODO this should be Qt::DisplayRole
        ActionRole
    };

    QVariant data(const QModelIndex &index, int role) const override;
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    QHash<int, QByteArray> roleNames() const override;

    void updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath);

    bool menuAvailable() const;
    void setMenuAvailable(bool set);

    QVariant winId() const;
    void setWinId(const QVariant &id);

signals:
    void requestActivateIndex(int index);

protected:
    bool nativeEventFilter(const QByteArray &eventType, void *message, long int *result) override;

private Q_SLOTS:
    void onWinIdChanged();
    void onX11WindowChanged(WId id);
    void onX11WindowRemoved(WId id);

    void update();

signals:
    void menuAvailableChanged();
    void modelNeedsUpdate();
    void winIdChanged();

private:
    bool m_menuAvailable;
    bool m_updatePending = false;

    QVariant m_winId{-1};

    //! window that its menu initialization may be delayed
    WId m_delayedMenuWindowId = 0;

    QPointer<QMenu> m_menu;

    QDBusServiceWatcher *m_serviceWatcher;
    QString m_serviceName;
    QString m_menuObjectPath;

    QPointer<KDBusMenuImporter> m_importer;
};

} // namespace Material


================================================
FILE: src/ApplicationMenuButton.h
================================================
/*
 * Copyright (C) 2019 Zain Ahmad <zain.x.ahmad@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// own
#include "Button.h"
#include "Material.h"

// KDecoration
#include <KDecoration2/DecoratedClient>

// Qt
#include <QPainter>

namespace Material
{

class ApplicationMenuButton
{

public:
    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {
        button->setVisible(decoratedClient->hasApplicationMenu());
    }
    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {
        button->setPenWidth(painter, gridUnit, 1.75);

        int spacing = qRound(gridUnit * 4);
        for (int i = -1; i <= 1; ++i) {
            const QPointF left { iconRect.left(), iconRect.center().y() + i * spacing };
            const QPointF right { iconRect.right(), iconRect.center().y() + i * spacing };

            painter->drawLine(left, right);
        }
    }
};

} // namespace Material


================================================
FILE: src/BoxShadowHelper.cc
================================================
/*
 * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

// own
#include "BoxShadowHelper.h"

// Qt
#include <QVector>

// std
#include <cmath>


namespace Material
{
namespace BoxShadowHelper
{

namespace
{
// According to the CSS Level 3 spec, standard deviation must be equal to
// half of the blur radius. https://www.w3.org/TR/css-backgrounds-3/#shadow-blur
// Current window size is too small for sigma equal to half of the blur radius.
// As a workaround, sigma blur scale is lowered. With the lowered sigma
// blur scale, area under the kernel equals to 0.98, which is pretty enough.
// Maybe, it should be changed in the future.
const qreal SIGMA_BLUR_SCALE = 0.4375;
} // anonymous namespace

inline qreal radiusToSigma(qreal radius)
{
    return radius * SIGMA_BLUR_SCALE;
}

inline int boxSizeToRadius(int boxSize)
{
    return (boxSize - 1) / 2;
}

QVector<int> computeBoxSizes(int radius, int numIterations)
{
    const qreal sigma = radiusToSigma(radius);

    // Box sizes are computed according to the "Fast Almost-Gaussian Filtering"
    // paper by Peter Kovesi.
    int lower = std::floor(std::sqrt(12 * std::pow(sigma, 2) / numIterations + 1));
    if (lower % 2 == 0) {
        lower--;
    }

    const int upper = lower + 2;
    const int threshold = std::round((12 * std::pow(sigma, 2) - numIterations * std::pow(lower, 2)
        - 4 * numIterations * lower - 3 * numIterations) / (-4 * lower - 4));

    QVector<int> boxSizes;
    boxSizes.reserve(numIterations);
    for (int i = 0; i < numIterations; ++i) {
        boxSizes.append(i < threshold ? lower : upper);
    }

    return boxSizes;
}

void boxBlurPass(const QImage &src, QImage &dst, int boxSize)
{
    const int alphaStride = src.depth() >> 3;
    const int alphaOffset = QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3;

    const int radius = boxSizeToRadius(boxSize);
    const qreal invSize = 1.0 / boxSize;

    const int dstStride = dst.width() * alphaStride;

    for (int y = 0; y < src.height(); ++y) {
        const uchar *srcAlpha = src.scanLine(y);
        uchar *dstAlpha = dst.scanLine(0);

        srcAlpha += alphaOffset;
        dstAlpha += alphaOffset + y * alphaStride;

        const uchar *left = srcAlpha;
        const uchar *right = left + alphaStride * radius;

        int window = 0;
        for (int x = 0; x < radius; ++x) {
            window += *srcAlpha;
            srcAlpha += alphaStride;
        }

        for (int x = 0; x <= radius; ++x) {
            window += *right;
            right += alphaStride;
            *dstAlpha = static_cast<uchar>(window * invSize);
            dstAlpha += dstStride;
        }

        for (int x = radius + 1; x < src.width() - radius; ++x) {
            window += *right - *left;
            left += alphaStride;
            right += alphaStride;
            *dstAlpha = static_cast<uchar>(window * invSize);
            dstAlpha += dstStride;
        }

        for (int x = src.width() - radius; x < src.width(); ++x) {
            window -= *left;
            left += alphaStride;
            *dstAlpha = static_cast<uchar>(window * invSize);
            dstAlpha += dstStride;
        }
    }
}

void boxBlurAlpha(QImage &image, int radius, int numIterations)
{
    // Temporary buffer is transposed so we always read memory
    // in linear order.
    QImage tmp(image.height(), image.width(), image.format());

    const QVector<int> boxSizes = computeBoxSizes(radius, numIterations);
    for (const int &boxSize : boxSizes) {
        boxBlurPass(image, tmp, boxSize); // horizontal pass
        boxBlurPass(tmp, image, boxSize); // vertical pass
    }
}

void boxShadow(QPainter *p, const QRect &box, const QPoint &offset, int radius, const QColor &color)
{
    const QSize size = box.size() + 2 * QSize(radius, radius);
    const qreal dpr = p->device()->devicePixelRatioF();

    QPainter painter;

    QImage shadow(size * dpr, QImage::Format_ARGB32_Premultiplied);
    shadow.setDevicePixelRatio(dpr);
    shadow.fill(Qt::transparent);

    painter.begin(&shadow);
    painter.fillRect(QRect(QPoint(radius, radius), box.size()), Qt::black);
    painter.end();

    // There is no need to blur RGB channels. Blur the alpha
    // channel and then give the shadow a tint of the desired color.
    const int numIterations = 3;
    boxBlurAlpha(shadow, radius, numIterations);

    painter.begin(&shadow);
    painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
    painter.fillRect(shadow.rect(), color);
    painter.end();

    QRect shadowRect = shadow.rect();
    shadowRect.setSize(shadowRect.size() / dpr);
    shadowRect.moveCenter(box.center() + offset);
    p->drawImage(shadowRect, shadow);
}

} // namespace BoxShadowHelper
} // namespace Material


================================================
FILE: src/BoxShadowHelper.h
================================================
/*
 * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// Qt
#include <QColor>
#include <QPainter>
#include <QPoint>
#include <QRect>

namespace Material
{
namespace BoxShadowHelper
{

void boxShadow(QPainter *p, const QRect &box, const QPoint &offset,
               int radius, const QColor &color);

} // namespace BoxShadowHelper
} // namespace Material


================================================
FILE: src/BuildConfig.h.cmake
================================================
#cmakedefine01 HAVE_Wayland
#cmakedefine01 HAVE_X11
#cmakedefine01 HAVE_KDecoration2_5_25
#cmakedefine01 HAVE_KF5_101


================================================
FILE: src/Button.cc
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

// own
#include "Button.h"
#include "Material.h"
#include "Decoration.h"

#include "AppIconButton.h"
#include "ApplicationMenuButton.h"
#include "OnAllDesktopsButton.h"
#include "ContextHelpButton.h"
#include "ShadeButton.h"
#include "KeepAboveButton.h"
#include "KeepBelowButton.h"
#include "CloseButton.h"
#include "MaximizeButton.h"
#include "MinimizeButton.h"

// KDecoration
#include <KDecoration2/DecoratedClient>
#include <KDecoration2/Decoration>
#include <KDecoration2/DecorationButton>

// KF
#include <KColorUtils>

// Qt
#include <QDebug>
#include <QMargins>
#include <QPainter>
#include <QVariantAnimation>
#include <QtMath> // qFloor


namespace Material
{

Button::Button(KDecoration2::DecorationButtonType type, Decoration *decoration, QObject *parent)
    : DecorationButton(type, decoration, parent)
    , m_animationEnabled(true)
    , m_animation(new QVariantAnimation(this))
    , m_opacity(1)
    , m_transitionValue(0)
    , m_padding(new QMargins())
    , m_isGtkButton(false)
{
    connect(this, &Button::hoveredChanged, this,
        [this](bool hovered) {
            updateAnimationState(hovered);
            update();
        });

    if (QCoreApplication::applicationName() == QStringLiteral("kded5")) {
        // See: https://github.com/Zren/material-decoration/issues/22
        // kde-gtk-config has a kded5 module which renders the buttons to svgs for gtk.
        m_isGtkButton = true;
    }

    // Animation based on SierraBreezeEnhanced
    // https://github.com/kupiqu/SierraBreezeEnhanced/blob/master/breezebutton.cpp#L45
    // The GTK bridge needs animations disabled to render hover states. See Issue #50.
    // https://invent.kde.org/plasma/kde-gtk-config/-/blob/master/kded/kwin_bridge/dummydecorationbridge.cpp#L35
    m_animationEnabled = !m_isGtkButton && decoration->animationsEnabled();
    m_animation->setDuration(decoration->animationsDuration());
    m_animation->setStartValue(0.0);
    m_animation->setEndValue(1.0);
    m_animation->setEasingCurve(QEasingCurve::InOutQuad);
    connect(m_animation, &QVariantAnimation::valueChanged, this, [this](const QVariant &value) {
        setTransitionValue(value.toReal());
    });
    connect(this, &Button::transitionValueChanged, this, [this]() {
        update();
    });

    connect(this, &Button::opacityChanged, this, [this]() {
        update();
    });

    setHeight(decoration->titleBarHeight());

    auto *decoratedClient = decoration->client().toStrongRef().data();

    switch (type) {
    case KDecoration2::DecorationButtonType::Menu:
        AppIconButton::init(this, decoratedClient);
        break;

    case KDecoration2::DecorationButtonType::ApplicationMenu:
        ApplicationMenuButton::init(this, decoratedClient);
        break;

    case KDecoration2::DecorationButtonType::OnAllDesktops:
        OnAllDesktopsButton::init(this, decoratedClient);
        break;

    case KDecoration2::DecorationButtonType::ContextHelp:
        ContextHelpButton::init(this, decoratedClient);
        break;

    case KDecoration2::DecorationButtonType::Shade:
        ShadeButton::init(this, decoratedClient);
        break;

    case KDecoration2::DecorationButtonType::KeepAbove:
        KeepAboveButton::init(this, decoratedClient);
        break;

    case KDecoration2::DecorationButtonType::KeepBelow:
        KeepBelowButton::init(this, decoratedClient);
        break;

    case KDecoration2::DecorationButtonType::Close:
        CloseButton::init(this, decoratedClient);
        break;

    case KDecoration2::DecorationButtonType::Maximize:
        MaximizeButton::init(this, decoratedClient);
        break;

    case KDecoration2::DecorationButtonType::Minimize:
        MinimizeButton::init(this, decoratedClient);
        break;

    default:
        break;
    }
}

Button::~Button()
{
}

KDecoration2::DecorationButton* Button::create(KDecoration2::DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent)
{
    auto deco = qobject_cast<Decoration*>(decoration);
    if (!deco) {
        return nullptr;
    }

    switch (type) {
    case KDecoration2::DecorationButtonType::Menu:
    // case KDecoration2::DecorationButtonType::ApplicationMenu:
    case KDecoration2::DecorationButtonType::OnAllDesktops:
    case KDecoration2::DecorationButtonType::ContextHelp:
    case KDecoration2::DecorationButtonType::Shade:
    case KDecoration2::DecorationButtonType::KeepAbove:
    case KDecoration2::DecorationButtonType::KeepBelow:
    case KDecoration2::DecorationButtonType::Close:
    case KDecoration2::DecorationButtonType::Maximize:
    case KDecoration2::DecorationButtonType::Minimize:
        return new Button(type, deco, parent);

    default:
        return nullptr;
    }
}

Button::Button(QObject *parent, const QVariantList &args)
    : Button(args.at(0).value<KDecoration2::DecorationButtonType>(), args.at(1).value<Decoration*>(), parent)
{
}

void Button::paint(QPainter *painter, const QRect &repaintRegion)
{
    Q_UNUSED(repaintRegion)

    // Buttons are coded assuming 24 units in size.
    const QRectF buttonRect = geometry();
    const QRectF contentRect = contentArea();

    const qreal iconScale = contentRect.height()/24;
    int iconSize;
    if (m_isGtkButton) {
        // See: https://github.com/Zren/material-decoration/issues/22
        // kde-gtk-config has a kded5 module which renders the buttons to svgs for gtk.

        // The svgs are 50x50, located at ~/.config/gtk-3.0/assets/
        // They are usually scaled down to just 18x18 when drawn in gtk headerbars.
        // The Gtk theme already has a fairly large amount of padding, as
        // the Breeze theme doesn't currently follow fitt's law. So use less padding
        // around the icon so that the icon is not a very tiny 8px.

        // 15% top/bottom padding, 70% leftover for the icon.
        // 24 = 3.5 topPadding + 17 icon + 3.5 bottomPadding
        // 17/24 * 18 = 12.75
        iconSize = qRound(iconScale * 17);
    } else {
        // 30% top/bottom padding, 40% leftover for the icon.
        // 24 = 7 topPadding + 10 icon + 7 bottomPadding
        iconSize = qRound(iconScale * 10);
    }
    QRectF iconRect = QRectF(0, 0, iconSize, iconSize);
    iconRect.moveCenter(contentRect.center().toPoint());

    const qreal gridUnit = iconRect.height()/10;

    painter->save();

    painter->setRenderHints(QPainter::Antialiasing, false);

    // Opacity
    painter->setOpacity(m_opacity);

    // Background.
    painter->setPen(Qt::NoPen);
    painter->setBrush(backgroundColor());
    painter->drawRect(buttonRect);

    // Foreground.
    setPenWidth(painter, gridUnit, 1);
    painter->setBrush(Qt::NoBrush);


    // Icon
    switch (type()) {
    case KDecoration2::DecorationButtonType::Menu:
        AppIconButton::paintIcon(this, painter, iconRect, gridUnit);
        break;

    case KDecoration2::DecorationButtonType::ApplicationMenu:
        ApplicationMenuButton::paintIcon(this, painter, iconRect, gridUnit);
        break;

    case KDecoration2::DecorationButtonType::OnAllDesktops:
        OnAllDesktopsButton::paintIcon(this, painter, iconRect, gridUnit);
        break;

    case KDecoration2::DecorationButtonType::ContextHelp:
        ContextHelpButton::paintIcon(this, painter, iconRect, gridUnit);
        break;

    case KDecoration2::DecorationButtonType::Shade:
        ShadeButton::paintIcon(this, painter, iconRect, gridUnit);
        break;

    case KDecoration2::DecorationButtonType::KeepAbove:
        KeepAboveButton::paintIcon(this, painter, iconRect, gridUnit);
        break;

    case KDecoration2::DecorationButtonType::KeepBelow:
        KeepBelowButton::paintIcon(this, painter, iconRect, gridUnit);
        break;

    case KDecoration2::DecorationButtonType::Close:
        CloseButton::paintIcon(this, painter, iconRect, gridUnit);
        break;

    case KDecoration2::DecorationButtonType::Maximize:
        MaximizeButton::paintIcon(this, painter, iconRect, gridUnit);
        break;

    case KDecoration2::DecorationButtonType::Minimize:
        MinimizeButton::paintIcon(this, painter, iconRect, gridUnit);
        break;

    default:
        paintIcon(painter, iconRect, gridUnit);
        break;
    }

    painter->restore();
}

void Button::paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit)
{
    Q_UNUSED(painter)
    Q_UNUSED(iconRect)
    Q_UNUSED(gridUnit)
}

void Button::updateSize(int contentWidth, int contentHeight)
{
    const QSize size(
        m_padding->left() + contentWidth + m_padding->right(),
        m_padding->top() + contentHeight + m_padding->bottom()
    );
    setGeometry(QRect(geometry().topLeft().toPoint(), size));
}

void Button::setHeight(int buttonHeight)
{
    // For simplicity, don't count the 1.33:1 scaling in the left/right padding.
    // The left/right padding is mainly for the border offset alignment.
    updateSize(qRound(buttonHeight * 1.33), buttonHeight);
}

qreal Button::iconLineWidth(const qreal gridUnit) const
{
    return PenWidth::Symbol * qMax(1.0, gridUnit);
}

void Button::setPenWidth(QPainter *painter, const qreal gridUnit, const qreal scale)
{
    QPen pen(foregroundColor());
    pen.setCapStyle(Qt::RoundCap);
    pen.setJoinStyle(Qt::MiterJoin);
    pen.setWidthF(iconLineWidth(gridUnit) * scale);
    painter->setPen(pen);
}

QColor Button::backgroundColor() const
{
    const auto *deco = qobject_cast<Decoration *>(decoration());
    if (!deco) {
        return {};
    }

    if (m_isGtkButton) {
        // Breeze GTK has huge margins around the button. It looks better
        // when we just change the fgColor on hover instead of the bgColor.
        return Qt::transparent;
    }

    //--- CloseButton
    if (type() == KDecoration2::DecorationButtonType::Close) {
        auto *decoratedClient = deco->client().toStrongRef().data();
        const QColor hoveredColor = decoratedClient->color(
            KDecoration2::ColorGroup::Warning,
            KDecoration2::ColorRole::Foreground
        );
        QColor normalColor = QColor(hoveredColor);
        normalColor.setAlphaF(0);

        if (isPressed()) {
            const QColor pressedColor = decoratedClient->color(
                KDecoration2::ColorGroup::Warning,
                KDecoration2::ColorRole::Foreground
            ).lighter();
            return KColorUtils::mix(normalColor, pressedColor, m_transitionValue);
        }

        if (isHovered()) {
            return KColorUtils::mix(normalColor, hoveredColor, m_transitionValue);
        }
    }

    //--- Checked
    if (isChecked() && type() != KDecoration2::DecorationButtonType::Maximize) {
        const QColor normalColor = deco->titleBarForegroundColor();

        if (isPressed()) {
            const QColor pressedColor = KColorUtils::mix(
                deco->titleBarBackgroundColor(),
                deco->titleBarForegroundColor(),
                0.7);
            return KColorUtils::mix(normalColor, pressedColor, m_transitionValue);
        }
        if (isHovered()) {
            const QColor hoveredColor = KColorUtils::mix(
                deco->titleBarBackgroundColor(),
                deco->titleBarForegroundColor(),
                0.8);
            return KColorUtils::mix(normalColor, hoveredColor, m_transitionValue);
        }
        return normalColor;
    }

    //--- Normal
    const QColor hoveredColor = KColorUtils::mix(
        deco->titleBarBackgroundColor(),
        deco->titleBarForegroundColor(),
        0.2);
    QColor normalColor = QColor(hoveredColor);
    normalColor.setAlphaF(0);

    if (isPressed()) {
        const QColor pressedColor = KColorUtils::mix(
            deco->titleBarBackgroundColor(),
            deco->titleBarForegroundColor(),
            0.3);
        return KColorUtils::mix(normalColor, pressedColor, m_transitionValue);
    }
    if (isHovered()) {
        return KColorUtils::mix(normalColor, hoveredColor, m_transitionValue);
    }
    return normalColor;
}

QColor Button::foregroundColor() const
{
    const auto *deco = qobject_cast<Decoration *>(decoration());
    if (!deco) {
        return {};
    }

    //--- Checked
    if (isChecked() && type() != KDecoration2::DecorationButtonType::Maximize) {
        const QColor activeColor = KColorUtils::mix(
            deco->titleBarBackgroundColor(),
            deco->titleBarForegroundColor(),
            0.2);

        if (isPressed() || isHovered()) {
            return KColorUtils::mix(
                activeColor,
                deco->titleBarBackgroundColor(),
                m_transitionValue);
        }
        return activeColor;
    }

    //--- Normal
    const QColor normalColor = KColorUtils::mix(
        deco->titleBarBackgroundColor(),
        deco->titleBarForegroundColor(),
        0.8);

    if (isPressed() || isHovered()) {
        // Breeze GTK has huge margins around the button. It looks better
        // when we just change the fgColor on hover instead of the bgColor.
        QColor hoveredColor;
        if (m_isGtkButton && type() == KDecoration2::DecorationButtonType::Close) {
            auto *decoratedClient = deco->client().toStrongRef().data();
            hoveredColor = decoratedClient->color(
                KDecoration2::ColorGroup::Warning,
                KDecoration2::ColorRole::Foreground
            );
        } else if (m_isGtkButton && type() == KDecoration2::DecorationButtonType::Maximize) {
            const int grayValue = qGray(deco->titleBarBackgroundColor().rgb());
            if (grayValue < 128) { // Dark Bg
                hoveredColor = QColor(100, 196, 86); // from SierraBreeze
            } else { // Light Bg
                hoveredColor = QColor(40, 200, 64); // from SierraBreeze
            }
        } else if (m_isGtkButton && type() == KDecoration2::DecorationButtonType::Minimize) {
            const int grayValue = qGray(deco->titleBarBackgroundColor().rgb());
            if (grayValue < 128) {
                hoveredColor = QColor(223, 192, 76); // from SierraBreeze
            } else { // Light Bg
                hoveredColor = QColor(255, 188, 48); // from SierraBreeze
            }
        } else {
            hoveredColor = deco->titleBarForegroundColor();
        }

        return KColorUtils::mix(
            normalColor,
            hoveredColor,
            m_transitionValue);
    }

    return normalColor;
}


QRectF Button::contentArea() const
{
    return geometry().adjusted(
        m_padding->left(),
        m_padding->top(),
        -m_padding->right(),
        -m_padding->bottom()
    );
}

bool Button::animationEnabled() const
{
    return m_animationEnabled;
}

void Button::setAnimationEnabled(bool value)
{
    if (m_animationEnabled != value) {
        m_animationEnabled = value;
        emit animationEnabledChanged();
    }
}

int Button::animationDuration() const
{
    return m_animation->duration();
}

void Button::setAnimationDuration(int value)
{
    if (m_animation->duration() != value) {
        m_animation->setDuration(value);
        emit animationDurationChanged();
    }
}

qreal Button::opacity() const
{
    return m_opacity;
}

void Button::setOpacity(qreal value)
{
    if (m_opacity != value) {
        m_opacity = value;
        emit opacityChanged();
    }
}

qreal Button::transitionValue() const
{
    return m_transitionValue;
}

void Button::setTransitionValue(qreal value)
{
    if (m_transitionValue != value) {
        m_transitionValue = value;
        emit transitionValueChanged(value);
    }
}

QMargins* Button::padding()
{
    return m_padding;
}

void Button::setHorzPadding(int value)
{
    padding()->setLeft(value);
    padding()->setRight(value);
}

void Button::setVertPadding(int value)
{
    padding()->setTop(value);
    padding()->setBottom(value);
}

void Button::updateAnimationState(bool hovered)
{
    if (m_animationEnabled) {
        QAbstractAnimation::Direction dir = hovered ? QAbstractAnimation::Forward : QAbstractAnimation::Backward;
        if (m_animation->state() == QAbstractAnimation::Running && m_animation->direction() != dir) {
            m_animation->stop();
        }
        m_animation->setDirection(dir);
        if (m_animation->state() != QAbstractAnimation::Running) {
            m_animation->start();
        }
    } else {
        setTransitionValue(1);
    }
}


} // namespace Material


================================================
FILE: src/Button.h
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// KDecoration
#include <KDecoration2/Decoration>
#include <KDecoration2/DecorationButton>

// Qt
#include <QMargins>
#include <QRectF>
#include <QVariantAnimation>

namespace Material
{

class Decoration;

class Button : public KDecoration2::DecorationButton
{
    Q_OBJECT

public:
    Button(KDecoration2::DecorationButtonType type, Decoration *decoration, QObject *parent = nullptr);
    ~Button() override;

    Q_PROPERTY(bool animationEnabled READ animationEnabled WRITE setAnimationEnabled NOTIFY animationEnabledChanged)
    Q_PROPERTY(int animationDuration READ animationDuration WRITE setAnimationDuration NOTIFY animationDurationChanged)
    Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged)
    Q_PROPERTY(qreal transitionValue READ transitionValue WRITE setTransitionValue NOTIFY transitionValueChanged)
    Q_PROPERTY(QMargins* padding READ padding NOTIFY paddingChanged)

    // Passed to DecorationButtonGroup in Decoration
    static KDecoration2::DecorationButton *create(KDecoration2::DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent = nullptr);

    // This is called by:
    // registerPlugin<Material::Button>(QStringLiteral("button"))
    // It is needed to create buttons for applet-window-buttons.
    explicit Button(QObject *parent, const QVariantList &args);


    void paint(QPainter *painter, const QRect &repaintRegion) override;
    virtual void paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit);

    virtual void updateSize(int contentWidth, int contentHeight);
    virtual void setHeight(int buttonHeight);

    virtual qreal iconLineWidth(const qreal gridUnit) const;
    void setPenWidth(QPainter *painter, const qreal gridUnit, const qreal scale);

    virtual QColor backgroundColor() const;
    virtual QColor foregroundColor() const;

    QRectF contentArea() const;

    bool animationEnabled() const;
    void setAnimationEnabled(bool value);

    int animationDuration() const;
    void setAnimationDuration(int duration);

    qreal opacity() const;
    void setOpacity(qreal value);

    qreal transitionValue() const;
    void setTransitionValue(qreal value);

    QMargins* padding();
    void setHorzPadding(int value);
    void setVertPadding(int value);

private Q_SLOTS:
    void updateAnimationState(bool hovered);

signals:
    void animationEnabledChanged();
    void animationDurationChanged();
    void opacityChanged();
    void transitionValueChanged(qreal);
    void paddingChanged();

private:
    bool m_animationEnabled;
    QVariantAnimation *m_animation;
    qreal m_opacity;
    qreal m_transitionValue;
    QMargins *m_padding;
    bool m_isGtkButton;
};

} // namespace Material


================================================
FILE: src/CMakeLists.txt
================================================
find_package (KDecoration2 REQUIRED)

find_package (Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS
    Core
    Gui
)

find_package (KF5 REQUIRED COMPONENTS
    Config
    ConfigWidgets
    CoreAddons
    GuiAddons
    I18n
    IconThemes
    WindowSystem
)


# X11
find_package(X11 REQUIRED)
set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries"
    URL "http://www.x.org"
    TYPE REQUIRED
    PURPOSE "Required for building the X11 based workspace"
)

find_package(XCB MODULE REQUIRED COMPONENTS
    XCB
    RANDR
)
set_package_properties(XCB PROPERTIES TYPE REQUIRED)

if (Qt6_FOUND)
    # The QX11Info class has been removed.
    # Clients that still rely on the functionality can include the private header <QtGui/private/qtx11extras_p.h> as a stopgap solution.
    # To enable private headers use QT += gui-private with qmake, or add a project dependency to Qt::GuiPrivate with CMake.
    # https://doc.qt.io/qt-6/extras-changes-qt6.html#changes-to-qt-x11-extras
    find_package(Qt${QT_VERSION_MAJOR} ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS GuiPrivate)
elseif(Qt5_FOUND)
    find_package(Qt${QT_VERSION_MAJOR} ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS X11Extras)
endif()


if(X11_FOUND AND XCB_XCB_FOUND)
    set(HAVE_X11 ON)
    set(X11_LIBRARIES XCB::XCB)
endif()


# Wayland
find_package (KF5 COMPONENTS
    Wayland
)

if(KF5Wayland_FOUND)
    set(HAVE_Wayland ON)
    set(Wayland_LIBRARIES KF5::WaylandClient)
endif()


# KDecoration2/Plasma Version
if(${KDecoration2_VERSION} VERSION_GREATER_EQUAL "5.25.0")
    set(HAVE_KDecoration2_5_25 ON)
else()
    set(HAVE_KDecoration2_5_25 OFF)
endif()
message(STATUS "HAVE_KDecoration2_5_25: ${HAVE_KDecoration2_5_25} (${KDecoration2_VERSION})")

# KF5 Version
if(${KF5_VERSION} VERSION_GREATER_EQUAL "5.101.0")
    set(HAVE_KF5_101 ON)
else()
    set(HAVE_KF5_101 OFF)
endif()
message(STATUS "HAVE_KF5_101: ${HAVE_KF5_101} (${KF5_VERSION})")


configure_file(BuildConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/BuildConfig.h)

set (decoration_SRCS
    AppMenuModel.cc
    AppMenuButton.cc
    AppMenuButtonGroup.cc
    BoxShadowHelper.cc
    Button.cc
    Decoration.cc
    MenuOverflowButton.cc
    TextButton.cc
    ConfigurationModule.cc
    plugin.cc
)

kconfig_add_kcfg_files(decoration_SRCS
    InternalSettings.kcfgc
)

add_library (materialdecoration MODULE
    ${decoration_SRCS}
)

target_link_libraries (materialdecoration
    PUBLIC
        dbusmenuqt
        Qt${QT_VERSION_MAJOR}::Core
        Qt${QT_VERSION_MAJOR}::Gui
        # Qt${QT_VERSION_MAJOR}::X11Extras
        KF5::ConfigCore
        KF5::ConfigGui
        KF5::ConfigWidgets
        KF5::CoreAddons
        KF5::I18n
        KF5::GuiAddons
        KF5::IconThemes
        KF5::WindowSystem
        ${X11_LIBRARIES}
        ${Wayland_LIBRARIES}

    PRIVATE
        KDecoration2::KDecoration
)
if (Qt6_FOUND)
    # The QX11Info class has been removed.
    target_link_libraries (materialdecoration
        PUBLIC
            Qt${QT_VERSION_MAJOR}::GuiPrivate
    )
elseif(Qt5_FOUND)
    target_link_libraries (materialdecoration
        PUBLIC
            Qt${QT_VERSION_MAJOR}::X11Extras
    )
endif()

install (TARGETS materialdecoration
         DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kdecoration2)


================================================
FILE: src/CloseButton.h
================================================
/*
 * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// own
#include "Button.h"

// KDecoration
#include <KDecoration2/DecoratedClient>

// Qt
#include <QPainter>

namespace Material
{

class CloseButton
{

public:
    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {
        QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::closeableChanged,
                button, &Button::setVisible);

        button->setVisible(decoratedClient->isCloseable());
    }
    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {
        Q_UNUSED(button)
        Q_UNUSED(gridUnit)

        painter->setRenderHints(QPainter::Antialiasing, true);

        button->setPenWidth(painter, gridUnit, 1.10);

        painter->drawLine(iconRect.topLeft(), iconRect.bottomRight());
        painter->drawLine(iconRect.topRight(), iconRect.bottomLeft());
    }
};

} // namespace Material


================================================
FILE: src/ConfigurationModule.cc
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 * Copyright (C) 2012 Martin Gräßlin <mgraesslin@kde.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

// own
#include "ConfigurationModule.h"
#include "Material.h"
#include "InternalSettings.h"

// KF
#include <KColorButton>
#include <KConfigSkeleton>
#include <KCoreConfigSkeleton>
#include <KCModule>
#include <KLocalizedString>
#include <KSharedConfig>

// KDecoration
#include <KDecoration2/DecorationButton>

// Qt
#include <QDebug>

// QWidget
#include <QButtonGroup>
#include <QCheckBox>
#include <QComboBox>
#include <QDoubleSpinBox>
#include <QFormLayout>
#include <QLabel>
#include <QRadioButton>
#include <QSpinBox>
#include <QTabWidget>
#include <QWidget>
#include <QVBoxLayout>

namespace Material
{


ConfigurationModule::ConfigurationModule(QWidget *parent, const QVariantList &args)
    : KCModule(parent, args)
    , m_titleAlignment(InternalSettings::AlignCenterFullWidth)
    , m_buttonSize(InternalSettings::ButtonDefault)
    , m_shadowSize(InternalSettings::ShadowVeryLarge)
{
    init();
}

void ConfigurationModule::init()
{
    KCoreConfigSkeleton *skel = new KCoreConfigSkeleton(KSharedConfig::openConfig(s_configFilename), this);
    skel->setCurrentGroup(QStringLiteral("Windeco"));

    // See fr.po for the messages we can reuse from breeze:
    // https://websvn.kde.org/*checkout*/trunk/l10n-kf5/fr/messages/breeze/breeze_kwin_deco.po

    //--- Tabs
    QTabWidget *tabWidget = new QTabWidget(this);
    QVBoxLayout *mainLayout = new QVBoxLayout(this);
    mainLayout->addWidget(tabWidget);
    mainLayout->addStretch(1);
    setLayout(mainLayout);

    //--- General
    QWidget *generalTab = new QWidget(tabWidget);
    tabWidget->addTab(generalTab, i18nd("breeze_kwin_deco", "General"));
    QFormLayout *generalForm = new QFormLayout(generalTab);
    generalTab->setLayout(generalForm);

    QComboBox *titleAlignment = new QComboBox(generalTab);
    titleAlignment->addItem(i18nd("breeze_kwin_deco", "Left"));
    titleAlignment->addItem(i18nd("breeze_kwin_deco", "Center"));
    titleAlignment->addItem(i18nd("breeze_kwin_deco", "Center (Full Width)"));
    titleAlignment->addItem(i18nd("breeze_kwin_deco", "Right"));
    titleAlignment->addItem(i18n("Hidden"));
    titleAlignment->setObjectName(QStringLiteral("kcfg_TitleAlignment"));
    generalForm->addRow(i18nd("breeze_kwin_deco", "Tit&le alignment:"), titleAlignment);

    QComboBox *buttonSizes = new QComboBox(generalTab);
    buttonSizes->addItem(i18nd("breeze_kwin_deco", "Tiny"));
    buttonSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "Small"));
    buttonSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "Medium"));
    buttonSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "Large"));
    buttonSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "Very Large"));
    buttonSizes->setObjectName(QStringLiteral("kcfg_ButtonSize"));
    generalForm->addRow(i18nd("breeze_kwin_deco", "B&utton size:"), buttonSizes);

    QDoubleSpinBox *activeOpacity = new QDoubleSpinBox(generalTab);
    activeOpacity->setMinimum(0.0);
    activeOpacity->setMaximum(1.0);
    activeOpacity->setSingleStep(0.05);
    activeOpacity->setObjectName(QStringLiteral("kcfg_ActiveOpacity"));
    generalForm->addRow(i18n("Active Opacity:"), activeOpacity);

    QDoubleSpinBox *inactiveOpacity = new QDoubleSpinBox(generalTab);
    inactiveOpacity->setMinimum(0.0);
    inactiveOpacity->setMaximum(1.0);
    inactiveOpacity->setSingleStep(0.05);
    inactiveOpacity->setObjectName(QStringLiteral("kcfg_InactiveOpacity"));
    generalForm->addRow(i18n("Inactive Opacity:"), inactiveOpacity);


    //--- Menu
    QWidget *menuTab = new QWidget(tabWidget);
    tabWidget->addTab(menuTab, i18n("Menu"));
    QFormLayout *menuForm = new QFormLayout(menuTab);
    menuTab->setLayout(menuForm);

    QLabel *menuLabel = new QLabel(menuTab);
    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."));
    menuForm->addRow(QStringLiteral(""), menuLabel);

    QRadioButton *menuAlwaysShow = new QRadioButton(menuTab);
    menuAlwaysShow->setText(i18n("Always Show Menu"));
    menuAlwaysShow->setObjectName(QStringLiteral("kcfg_MenuAlwaysShow"));
    menuForm->addRow(QStringLiteral(""), menuAlwaysShow);

    // Since there's no easy way to bind this to !MenuAlwaysShow, we
    // workaround this by marking the button as checked on init.
    // When the config is loaded:
    // * If menuAlwaysShow is toggled true, this will be toggled false.
    // * If menuAlwaysShow is left false, then this remains true.
    QRadioButton *menuRevealOnHover = new QRadioButton(menuTab);
    menuRevealOnHover->setText(i18n("Reveal Menu on Hover"));
    menuRevealOnHover->setChecked(true);
    menuForm->addRow(QStringLiteral(""), menuRevealOnHover);

    QButtonGroup *menuAlwaysShowGroup = new QButtonGroup(menuTab);
    menuAlwaysShowGroup->addButton(menuAlwaysShow);
    menuAlwaysShowGroup->addButton(menuRevealOnHover);

    QSpinBox *menuButtonHorzPadding = new QSpinBox(menuTab);
    menuButtonHorzPadding->setMinimum(0);
    menuButtonHorzPadding->setMaximum(INT_MAX);
    menuButtonHorzPadding->setObjectName(QStringLiteral("kcfg_MenuButtonHorzPadding"));
    menuForm->addRow(i18n("Padding:"), menuButtonHorzPadding);


    //--- Animations
    QWidget *animationsTab = new QWidget(tabWidget);
    tabWidget->addTab(animationsTab, i18nd("breeze_kwin_deco", "Animations"));
    QFormLayout *animationsForm = new QFormLayout(animationsTab);
    animationsTab->setLayout(animationsForm);

    QCheckBox *animationsEnabled = new QCheckBox(animationsTab);
    animationsEnabled->setText(i18nd("breeze_kwin_deco", "Enable animations"));
    animationsEnabled->setObjectName(QStringLiteral("kcfg_AnimationsEnabled"));
    animationsForm->addRow(QStringLiteral(""), animationsEnabled);

    QSpinBox *animationsDuration = new QSpinBox(animationsTab);
    animationsDuration->setMinimum(0);
    animationsDuration->setMaximum(INT_MAX);
    animationsDuration->setSuffix(i18nd("breeze_kwin_deco", " ms"));
    animationsDuration->setObjectName(QStringLiteral("kcfg_AnimationsDuration"));
    animationsForm->addRow(i18nd("breeze_kwin_deco", "Animations:"), animationsDuration);


    //--- Shadows
    QWidget *shadowTab = new QWidget(tabWidget);
    tabWidget->addTab(shadowTab, i18nd("breeze_kwin_deco", "Shadows"));
    QFormLayout *shadowForm = new QFormLayout(shadowTab);
    shadowTab->setLayout(shadowForm);

    QComboBox *shadowSizes = new QComboBox(shadowTab);
    shadowSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "None"));
    shadowSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "Small"));
    shadowSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "Medium"));
    shadowSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "Large"));
    shadowSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "Very Large"));
    shadowSizes->setObjectName(QStringLiteral("kcfg_ShadowSize"));
    shadowForm->addRow(i18nd("breeze_kwin_deco", "Si&ze:"), shadowSizes);

    QSpinBox *shadowStrength = new QSpinBox(shadowTab);
    shadowStrength->setMinimum(25);
    shadowStrength->setMaximum(255);
    // shadowStrength->setSuffix(i18nd("breeze_kwin_deco", "%"));
    shadowStrength->setObjectName(QStringLiteral("kcfg_ShadowStrength"));
    shadowForm->addRow(i18ndc("breeze_kwin_deco", "strength of the shadow (from transparent to opaque)", "S&trength:"), shadowStrength);

    KColorButton *shadowColor = new KColorButton(shadowTab);
    shadowColor->setObjectName(QStringLiteral("kcfg_ShadowColor"));
    shadowForm->addRow(i18nd("breeze_kwin_deco", "Color:"), shadowColor);

    //--- Config Bindings
    skel->addItemInt(
        QStringLiteral("TitleAlignment"),
        m_titleAlignment,
        InternalSettings::AlignCenterFullWidth,
        QStringLiteral("TitleAlignment")
    );
    skel->addItemInt(
        QStringLiteral("ButtonSize"),
        m_buttonSize,
        InternalSettings::ButtonDefault,
        QStringLiteral("ButtonSize")
    );
    skel->addItemDouble(
        QStringLiteral("ActiveOpacity"),
        m_activeOpacity,
        0.75,
        QStringLiteral("ActiveOpacity")
    );
    skel->addItemDouble(
        QStringLiteral("InactiveOpacity"),
        m_inactiveOpacity,
        0.85,
        QStringLiteral("InactiveOpacity")
    );
    skel->addItemBool(
        QStringLiteral("MenuAlwaysShow"),
        m_menuAlwaysShow,
        true,
        QStringLiteral("MenuAlwaysShow")
    );
    skel->addItemInt(
        QStringLiteral("MenuButtonHorzPadding"),
        m_menuButtonHorzPadding,
        1,
        QStringLiteral("MenuButtonHorzPadding")
    );
    skel->addItemBool(
        QStringLiteral("AnimationsEnabled"),
        m_animationsEnabled,
        true,
        QStringLiteral("AnimationsEnabled")
    );
    skel->addItemInt(
        QStringLiteral("AnimationsDuration"),
        m_animationsDuration,
        150,
        QStringLiteral("AnimationsDuration")
    );
    skel->addItemInt(
        QStringLiteral("ShadowSize"),
        m_shadowSize,
        InternalSettings::ShadowVeryLarge,
        QStringLiteral("ShadowSize")
    );
    skel->addItemInt(
        QStringLiteral("ShadowStrength"),
        m_shadowStrength,
        255,
        QStringLiteral("ShadowStrength")
    );
    skel->addItem(new KConfigSkeleton::ItemColor(
        skel->currentGroup(),
        QStringLiteral("ShadowColor"),
        m_shadowColor,
        QColor(33, 33, 33)
    ), QStringLiteral("ShadowColor"));

    //---
    addConfig(skel, this);
}

} // namespace Material


================================================
FILE: src/ConfigurationModule.h
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 * Copyright (C) 2012 Martin Gräßlin <mgraesslin@kde.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

// KF
#include <KCModule>

// Qt
#include <QColor>
#include <QWidget>

namespace Material
{

class ConfigurationModule : public KCModule
{
    Q_OBJECT
public:
    ConfigurationModule(QWidget *parent, const QVariantList &args);

private:
    void init();

    int m_titleAlignment;
    int m_buttonSize;
    double m_activeOpacity;
    double m_inactiveOpacity;
    bool m_menuAlwaysShow;
    int m_menuButtonHorzPadding;
    bool m_animationsEnabled;
    int m_animationsDuration;
    int m_shadowSize;
    int m_shadowStrength;
    QColor m_shadowColor;
};

} // namespace Material


================================================
FILE: src/ContextHelpButton.h
================================================
/*
 * Copyright (C) 2021 Chris Holland <zrenfire@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// own
#include "Button.h"
#include "Material.h"

// KDecoration
#include <KDecoration2/DecoratedClient>

// Qt
#include <QPainter>
#include <QPainterPath>
#include <QPointF>
#include <QRectF>
#include <QSizeF>

namespace Material
{

class ContextHelpButton
{

public:
    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {
        QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::providesContextHelpChanged,
                button, &Button::setVisible);

        button->setVisible(decoratedClient->providesContextHelp());
    }
    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {
        button->setPenWidth(painter, gridUnit, 1.25);

        painter->setRenderHints(QPainter::Antialiasing, true);
        painter->translate( iconRect.topLeft() );

        const QRectF topCurveRect = QRectF(
            QPointF( 1.5, 0.5 ) * gridUnit,
            QSizeF( 8, 6 ) * gridUnit
        );
        QPainterPath path;
        path.moveTo( topCurveRect.center() - QPointF(topCurveRect.width()/2, 0) );
        path.arcTo(
            topCurveRect,
            180,
            -180
        );
        path.cubicTo(
            QPointF( 7.8125, 5.9375 ) * gridUnit,
            QPointF( 5.625, 4.6875 ) * gridUnit,
            QPointF( 5, 8 ) * gridUnit
        );
        painter->drawPath(path);

        // Dot
        painter->drawRect( QRectF(
            QPointF( 5, 10 ) * gridUnit,
            QSizeF( 0.5, 0.5 ) * gridUnit
        ));
    }
};

} // namespace Material


================================================
FILE: src/Decoration.cc
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>
 * Copyright (C) 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>
 * Copyright (C) 2014 Martin Gräßlin <mgraesslin@kde.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

// own
#include "Decoration.h"
#include "Material.h"
#include "BuildConfig.h"
#include "AppMenuButtonGroup.h"
#include "BoxShadowHelper.h"
#include "Button.h"
#include "InternalSettings.h"

// KDecoration
#include <KDecoration2/DecoratedClient>
#include <KDecoration2/DecorationButton>
#include <KDecoration2/DecorationButtonGroup>
#include <KDecoration2/DecorationSettings>
#include <KDecoration2/DecorationShadow>

// KF
#include <KWindowSystem>

// Qt
#include <QApplication>
#include <QDebug>
#include <QHoverEvent>
#include <QMouseEvent>
#include <QPainter>
#include <QRegion>
#include <QSharedPointer>
#include <QWheelEvent>

// X11
#if HAVE_X11
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <private/qtx11extras_p.h>
#else
#include <QX11Info>
#endif
#include <xcb/xcb.h>
#endif

namespace Material
{

namespace
{

struct ShadowParams
{
    ShadowParams() = default;

    ShadowParams(const QPoint &offset, int radius, qreal opacity)
        : offset(offset)
        , radius(radius)
        , opacity(opacity) {}

    QPoint offset;
    int radius = 0;
    qreal opacity = 0;
};

struct CompositeShadowParams
{
    CompositeShadowParams() = default;

    CompositeShadowParams(
            const QPoint &offset,
            const ShadowParams &shadow1,
            const ShadowParams &shadow2)
        : offset(offset)
        , shadow1(shadow1)
        , shadow2(shadow2) {}

    bool isNone() const {
        return qMax(shadow1.radius, shadow2.radius) == 0;
    }

    QPoint offset;
    ShadowParams shadow1;
    ShadowParams shadow2;
};

// const CompositeShadowParams s_shadowParams = CompositeShadowParams(
//     QPoint(0, 18),
//     ShadowParams(QPoint(0, 0), 64, 0.8),
//     ShadowParams(QPoint(0, -10), 24, 0.1)
// );
const CompositeShadowParams s_shadowParams[] = {
    // None
    CompositeShadowParams(),
    // Small
    CompositeShadowParams(
        QPoint(0, 4),
        ShadowParams(QPoint(0, 0), 16, 1),
        ShadowParams(QPoint(0, -2), 8, 0.4)),
    // Medium
    CompositeShadowParams(
        QPoint(0, 8),
        ShadowParams(QPoint(0, 0), 32, 0.9),
        ShadowParams(QPoint(0, -4), 16, 0.3)),
    // Large
    CompositeShadowParams(
        QPoint(0, 12),
        ShadowParams(QPoint(0, 0), 48, 0.8),
        ShadowParams(QPoint(0, -6), 24, 0.2)),
    // Very large
    CompositeShadowParams(
        QPoint(0, 16),
        ShadowParams(QPoint(0, 0), 64, 0.7),
        ShadowParams(QPoint(0, -8), 32, 0.1)),
};

inline CompositeShadowParams lookupShadowParams(int size)
{
    switch (size) {
    case InternalSettings::ShadowNone:
        return s_shadowParams[0];
    case InternalSettings::ShadowSmall:
        return s_shadowParams[1];
    case InternalSettings::ShadowMedium:
        return s_shadowParams[2];
    default:
    case InternalSettings::ShadowLarge:
        return s_shadowParams[3];
    case InternalSettings::ShadowVeryLarge:
        return s_shadowParams[4];
    }
}

} // anonymous namespace

static int s_decoCount = 0;
static int s_shadowSizePreset = InternalSettings::ShadowVeryLarge;
static int s_shadowStrength = 255;
static QColor s_shadowColor = QColor(33, 33, 33);
static QSharedPointer<KDecoration2::DecorationShadow> s_cachedShadow;

Decoration::Decoration(QObject *parent, const QVariantList &args)
    : KDecoration2::Decoration(parent, args)
    , m_internalSettings(nullptr)
{
    ++s_decoCount;
}

Decoration::~Decoration()
{
    if (--s_decoCount == 0) {
        s_cachedShadow.clear();
    }
}

QRect Decoration::titleBarRect() const
{
    return QRect(0, 0, size().width(), titleBarHeight());
}

QRect Decoration::centerRect() const
{
    const bool leftButtonsVisible = !m_leftButtons->buttons().isEmpty();
    const int leftOffset = m_leftButtons->geometry().right()
        + (leftButtonsVisible ? settings()->smallSpacing() : 0);

    const bool rightButtonsVisible = !m_rightButtons->buttons().isEmpty();
    const int rightOffset = m_rightButtons->geometry().width()
        + (rightButtonsVisible ? settings()->smallSpacing() : 0);

    return titleBarRect().adjusted(
        leftOffset,
        0,
        -rightOffset,
        0
    );
}

void Decoration::paint(QPainter *painter, const QRect &repaintRegion)
{
    auto *decoratedClient = client().toStrongRef().data();

    if (!decoratedClient->isShaded()) {
        paintFrameBackground(painter, repaintRegion);
    }

    paintTitleBarBackground(painter, repaintRegion);
    paintButtons(painter, repaintRegion);
    paintCaption(painter, repaintRegion);

    // Don't paint outline for NoBorder, NoSideBorder, or Tiny borders.
    if (settings()->borderSize() >= KDecoration2::BorderSize::Normal) {
        paintOutline(painter, repaintRegion);
    }
    updateBlur();
}

void Decoration::init()
{
    m_internalSettings = QSharedPointer<InternalSettings>(new InternalSettings());

    auto *decoratedClient = client().toStrongRef().data();

    auto repaintTitleBar = [this] {
        update(titleBar());
    };

    m_leftButtons = new KDecoration2::DecorationButtonGroup(
        KDecoration2::DecorationButtonGroup::Position::Left,
        this,
        &Button::create);

    m_rightButtons = new KDecoration2::DecorationButtonGroup(
        KDecoration2::DecorationButtonGroup::Position::Right,
        this,
        &Button::create);

    m_menuButtons = new AppMenuButtonGroup(this);
    connect(m_menuButtons, &AppMenuButtonGroup::menuUpdated,
            this, &Decoration::updateButtonsGeometry);
    connect(m_menuButtons, &AppMenuButtonGroup::opacityChanged,
            this, repaintTitleBar);
    connect(m_menuButtons, &AppMenuButtonGroup::alwaysShowChanged,
            this, repaintTitleBar);
    m_menuButtons->updateAppMenuModel();


    connect(decoratedClient, &KDecoration2::DecoratedClient::widthChanged,
            this, &Decoration::updateTitleBar);
    connect(decoratedClient, &KDecoration2::DecoratedClient::widthChanged,
            this, &Decoration::updateButtonsGeometry);
    connect(decoratedClient, &KDecoration2::DecoratedClient::maximizedChanged,
            this, &Decoration::updateButtonsGeometry);

    connect(decoratedClient, &KDecoration2::DecoratedClient::adjacentScreenEdgesChanged,
            this, &Decoration::updateBorders);
    connect(decoratedClient, &KDecoration2::DecoratedClient::maximizedHorizontallyChanged,
            this, &Decoration::updateBorders);
    connect(decoratedClient, &KDecoration2::DecoratedClient::maximizedVerticallyChanged,
            this, &Decoration::updateBorders);
    connect(decoratedClient, &KDecoration2::DecoratedClient::shadedChanged,
            this, &Decoration::updateBorders);

    connect(decoratedClient, &KDecoration2::DecoratedClient::captionChanged,
            this, repaintTitleBar);
    connect(decoratedClient, &KDecoration2::DecoratedClient::activeChanged,
            this, repaintTitleBar);

    updateBorders();
    updateResizeBorders();
    updateTitleBar();
    updateButtonsGeometry();

    connect(this, &KDecoration2::Decoration::sectionUnderMouseChanged,
            this, &Decoration::onSectionUnderMouseChanged);
    updateTitleBarHoverState();

    // For some reason, the shadow should be installed the last. Otherwise,
    // the Window Decorations KCM crashes.
    updateShadow();

    connect(settings().data(), &KDecoration2::DecorationSettings::reconfigured,
        this, &Decoration::reconfigure);
    connect(m_internalSettings.data(), &InternalSettings::configChanged,
        this, &Decoration::reconfigure);

    // Window Decoration KCM
    // The reconfigure signal will update active windows, but we need to hook
    // individual signals for the preview in the KCM.
    connect(settings().data(), &KDecoration2::DecorationSettings::borderSizeChanged,
        this, &Decoration::updateBorders);
    connect(settings().data(), &KDecoration2::DecorationSettings::fontChanged,
        this, &Decoration::updateBorders);
    connect(settings().data(), &KDecoration2::DecorationSettings::spacingChanged,
        this, &Decoration::updateBorders);
}

void Decoration::reconfigure()
{
    m_internalSettings->load();

    updateBorders();
    updateTitleBar();
    m_menuButtons->setAlwaysShow(m_internalSettings->menuAlwaysShow());
    updateButtonsGeometry();
    updateButtonAnimation();
    updateShadow();
    update();
}

void Decoration::mousePressEvent(QMouseEvent *event)
{
    KDecoration2::Decoration::mousePressEvent(event);
    // qCDebug(category) << "Decoration::mousePressEvent" << event;

    if (m_menuButtons->geometry().contains(event->pos())) {
        if (event->button() == Qt::LeftButton) {
            initDragMove(event->pos());
            event->setAccepted(false);

        // If AppMenuButton's do not handle the button
        } else if (event->button() == Qt::MiddleButton || event->button() == Qt::RightButton) {
            // Don't accept the event. KDecoration2 will
            // accept the event even if it doesn't pass
            // button->acceptableButtons()->testFlag(button)
            event->setAccepted(false);
        }
    }
}

void Decoration::hoverEnterEvent(QHoverEvent *event)
{
    KDecoration2::Decoration::hoverEnterEvent(event);
    qCDebug(category) << "Decoration::hoverEnterEvent" << event;
    updateBlur();
    // m_menuButtons->setHovered(true);
}

void Decoration::hoverMoveEvent(QHoverEvent *event)
{
    KDecoration2::Decoration::hoverMoveEvent(event);
    // qCDebug(category) << "Decoration::hoverMoveEvent" << event;

    const bool dragStarted = dragMoveTick(event->pos());
    // qCDebug(category) << "    " << "dragStarted" << dragStarted;
    if (dragStarted) {
        m_menuButtons->unPressAllButtons();
    }

    // const bool wasHovered = m_menuButtons->hovered();
    // const bool contains = m_menuButtons->geometry().contains(event->posF());
    // if (!wasHovered && contains) {
    //     // HoverEnter
    //     m_menuButtons->setHovered(true);
    // } else if (wasHovered && !contains) {
    //     // HoverLeave
    //     m_menuButtons->setHovered(false);
    // } else if (wasHovered && contains) {
    //     // HoverMove
    // }
    updateBlur();
}

void Decoration::mouseReleaseEvent(QMouseEvent *event)
{
    KDecoration2::Decoration::mouseReleaseEvent(event);
    // qCDebug(category) << "Decoration::mouseReleaseEvent" << event;

    resetDragMove();
    updateBlur();
}

void Decoration::hoverLeaveEvent(QHoverEvent *event)
{
    KDecoration2::Decoration::hoverLeaveEvent(event);
    qCDebug(category) << "Decoration::hoverLeaveEvent" << event;

    resetDragMove();
    updateBlur();
    // m_menuButtons->setHovered(false);
}

void Decoration::wheelEvent(QWheelEvent *event)
{
    #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
    const QPointF pos = event->position();
    #else
    const QPointF pos = event->posF();
    #endif

    if (m_menuButtons->geometry().contains(pos)) {
        // Skip
    } else {
        KDecoration2::Decoration::wheelEvent(event);
    }
}

void Decoration::onSectionUnderMouseChanged(const Qt::WindowFrameSection value)
{
    Q_UNUSED(value);
    // qCDebug(category) << "onSectionUnderMouseChanged" << value;
    updateTitleBarHoverState();
}

void Decoration::updateBlur()
{
#if HAVE_KDecoration2_5_25
    setBlurRegion(QRegion(0, 0, size().width(), size().height()));
#endif
}

void Decoration::updateBorders()
{
    const int sideSize = sideBorderSize();
    QMargins borders;
    borders.setTop(titleBarHeight());
    borders.setLeft(leftBorderVisible() ? sideSize : 0);
    borders.setRight(rightBorderVisible() ? sideSize : 0);
    borders.setBottom(bottomBorderVisible() ? bottomBorderSize() : 0);
    setBorders(borders);
}

void Decoration::updateResizeBorders()
{
    QMargins borders;

    const int extender = settings()->largeSpacing();
    borders.setLeft(extender);
    borders.setTop(extender);
    borders.setRight(extender);
    borders.setBottom(extender);

    setResizeOnlyBorders(borders);
}

void Decoration::updateTitleBar()
{
    setTitleBar(titleBarRect());
}

void Decoration::updateTitleBarHoverState()
{
    const bool wasHovered = m_menuButtons->hovered();
    const bool isHovered = titleBarIsHovered();
    if (!wasHovered && isHovered) {
        // HoverEnter
        m_menuButtons->setHovered(true);
    } else if (wasHovered && !isHovered) {
        // HoverLeave
        m_menuButtons->setHovered(false);
    } else if (wasHovered && isHovered) {
        // HoverMove
    }
}

void Decoration::setButtonGroupHeight(KDecoration2::DecorationButtonGroup *buttonGroup, int buttonHeight)
{
    // int vertPadding = buttonPadding();
    for (int i = 0; i < buttonGroup->buttons().length(); i++) {
        KDecoration2::DecorationButton* decoButton = buttonGroup->buttons().value(i);
        auto *button = qobject_cast<Button *>(decoButton);
        if (button) {
            button->setHeight(buttonHeight);
            // button->setVertPadding(vertPadding);
        }
    }
}

void Decoration::setButtonGroupHorzPadding(KDecoration2::DecorationButtonGroup *buttonGroup, int value)
{
    for (int i = 0; i < buttonGroup->buttons().length(); i++) {
        KDecoration2::DecorationButton* decoButton = buttonGroup->buttons().value(i);
        auto *button = qobject_cast<Button *>(decoButton);
        if (button) {
            button->setHorzPadding(value);
        }
    }
}

void Decoration::setButtonGroupVertPadding(KDecoration2::DecorationButtonGroup *buttonGroup, int value)
{
    for (int i = 0; i < buttonGroup->buttons().length(); i++) {
        KDecoration2::DecorationButton* decoButton = buttonGroup->buttons().value(i);
        auto *button = qobject_cast<Button *>(decoButton);
        if (button) {
            button->setVertPadding(value);
        }
    }
}

void Decoration::updateButtonHeight()
{
    const int buttonHeight = titleBarHeight();
    setButtonGroupHeight(m_leftButtons, buttonHeight);
    setButtonGroupHeight(m_rightButtons, buttonHeight);
    setButtonGroupHeight(m_menuButtons, buttonHeight);
}

void Decoration::updateButtonsGeometry()
{
    const int sideSize = sideBorderSize();
    const int leftOffset = leftBorderVisible() ? sideSize : 0;
    const int rightOffset = rightBorderVisible() ? sideSize : 0;

    updateButtonHeight();

    // Left
    m_leftButtons->setPos(QPointF(leftOffset, 0));
    m_leftButtons->setSpacing(0);
    // if (!m_leftButtons->buttons().isEmpty()) {
    //     auto *firstButon = qobject_cast<Button *>(m_leftButtons->buttons().front());
    //     firstButon->padding()->setLeft(leftOffset);
    // }

    // Right
    m_rightButtons->setPos(QPointF(
        size().width() - rightOffset - m_rightButtons->geometry().width(),
        0
    ));
    m_rightButtons->setSpacing(0);
    // if (!m_rightButtons->buttons().isEmpty()) {
    //     auto *lastButton = qobject_cast<Button *>(m_rightButtons->buttons().last());
    //     lastButton->padding()->setRight(rightOffset);
    // }

    // Menu
    if (!m_menuButtons->buttons().isEmpty()) {
        const int captionOffset = captionMinWidth() + settings()->smallSpacing();
        const QRect availableRect = centerRect().adjusted(
            0,
            0,
            -captionOffset,
            0
        );
        setButtonGroupHorzPadding(m_menuButtons, m_internalSettings->menuButtonHorzPadding());
        m_menuButtons->setPos(availableRect.topLeft());
        m_menuButtons->setSpacing(0);
        m_menuButtons->updateOverflow(availableRect);
    }

    update();
}

void Decoration::setButtonGroupAnimation(KDecoration2::DecorationButtonGroup *buttonGroup, bool enabled, int duration)
{
    for (int i = 0; i < buttonGroup->buttons().length(); i++) {
        auto *button = qobject_cast<Button *>(buttonGroup->buttons().value(i));
        button->setAnimationEnabled(enabled);
        button->setAnimationDuration(duration);
    }
}

void Decoration::updateButtonAnimation()
{
    const bool enabled = animationsEnabled();
    const int duration = animationsDuration();
    setButtonGroupAnimation(m_leftButtons, enabled, duration);
    setButtonGroupAnimation(m_rightButtons, enabled, duration);
    setButtonGroupAnimation(m_menuButtons, enabled, duration);

    // Hover Animation
    m_menuButtons->setAnimationEnabled(enabled);
    m_menuButtons->setAnimationDuration(duration);
}

void Decoration::updateShadow()
{
    const QColor shadowColor = m_internalSettings->shadowColor();
    const int shadowStrengthInt = m_internalSettings->shadowStrength();
    const int shadowSizePreset = m_internalSettings->shadowSize();

    if (!s_cachedShadow.isNull()
        && s_shadowColor == shadowColor
        && s_shadowSizePreset == shadowSizePreset
        && s_shadowStrength == shadowStrengthInt
    ) {
        setShadow(s_cachedShadow);
        return;
    }

    s_shadowColor = shadowColor;
    s_shadowStrength = shadowStrengthInt;
    s_shadowSizePreset = shadowSizePreset;

    auto withOpacity = [] (const QColor &color, qreal opacity) -> QColor {
        QColor c(color);
        c.setAlphaF(opacity);
        return c;
    };

    const qreal shadowStrength = static_cast<qreal>(shadowStrengthInt) / 255.0;
    const CompositeShadowParams params = lookupShadowParams(shadowSizePreset);

    if (params.isNone()) { // InternalSettings::ShadowNone
        s_cachedShadow.clear();
        setShadow(s_cachedShadow);
        return;
    }

    // In order to properly render a box shadow with a given radius `shadowSize`,
    // the box size should be at least `2 * QSize(shadowSize, shadowSize)`.
    const int shadowSize = qMax(params.shadow1.radius, params.shadow2.radius);
    const QSize boxSize = QSize(1, 1) + QSize(shadowSize*2, shadowSize*2);
    const QRect box(QPoint(shadowSize, shadowSize), boxSize);
    const QRect rect = box.adjusted(-shadowSize, -shadowSize, shadowSize, shadowSize);

    QImage shadowTexture(rect.size(), QImage::Format_ARGB32_Premultiplied);
    shadowTexture.fill(Qt::transparent);

    QPainter painter(&shadowTexture);
    painter.setRenderHint(QPainter::Antialiasing);

    // Draw the "shape" shadow.
    BoxShadowHelper::boxShadow(
        &painter,
        box,
        params.shadow1.offset,
        params.shadow1.radius,
        withOpacity(shadowColor, params.shadow1.opacity * shadowStrength));

    // Draw the "contrast" shadow.
    BoxShadowHelper::boxShadow(
        &painter,
        box,
        params.shadow2.offset,
        params.shadow2.radius,
        withOpacity(shadowColor, params.shadow2.opacity * shadowStrength));

    // Mask out inner rect.
    const QMargins padding = QMargins(
        shadowSize - params.offset.x(),
        shadowSize - params.offset.y(),
        shadowSize + params.offset.x(),
        shadowSize + params.offset.y());
    const QRect innerRect = rect - padding;

    // Mask out window+titlebar from shadow
    painter.setPen(Qt::NoPen);
    painter.setBrush(Qt::black);
    painter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
    painter.drawRect(innerRect);

    painter.end();

    s_cachedShadow = QSharedPointer<KDecoration2::DecorationShadow>::create();
    s_cachedShadow->setPadding(padding);
    s_cachedShadow->setInnerShadowRect(QRect(shadowTexture.rect().center(), QSize(1, 1)));
    s_cachedShadow->setShadow(shadowTexture);

    setShadow(s_cachedShadow);
}

bool Decoration::menuAlwaysShow() const
{
    return m_internalSettings->menuAlwaysShow();
}

bool Decoration::animationsEnabled() const
{
    return m_internalSettings->animationsEnabled();
}

int Decoration::animationsDuration() const
{
    return m_internalSettings->animationsDuration();
}

int Decoration::buttonPadding() const
{
    const int baseUnit = settings()->gridUnit();
    switch (m_internalSettings->buttonSize()) {
    case InternalSettings::ButtonTiny:
        return qRound(baseUnit * 0.1);
    case InternalSettings::ButtonSmall:
        return qRound(baseUnit * 0.2);
    default:
    case InternalSettings::ButtonDefault:
        return qRound(baseUnit * 0.3);
    case InternalSettings::ButtonLarge:
        return qRound(baseUnit * 0.5);
    case InternalSettings::ButtonVeryLarge:
        return qRound(baseUnit * 0.8);
    }
}

int Decoration::titleBarHeight() const
{
    const QFontMetrics fontMetrics(settings()->font());
    return buttonPadding()*2 + fontMetrics.height();
}

int Decoration::appMenuButtonHorzPadding() const
{
    // smallSpacing is scaled by dpr with a min of 2px.
    // So we need to divide our "pixel units" by 2 before scaling by it.
    return settings()->smallSpacing() * m_internalSettings->menuButtonHorzPadding() / 2;
}

int Decoration::appMenuCaptionSpacing() const
{
    return settings()->largeSpacing() * 4;
}

int Decoration::captionMinWidth() const
{
    return settings()->largeSpacing() * 8;
}

int Decoration::bottomBorderSize() const {
    const int baseSize = settings()->smallSpacing();
    switch (settings()->borderSize()) {
        default:
        case KDecoration2::BorderSize::None:
            return 0;
        case KDecoration2::BorderSize::NoSides:
        case KDecoration2::BorderSize::Tiny:
            return 1; // Breeze: max(4, baseSize)
        case KDecoration2::BorderSize::Normal:
            return baseSize; // Breeze: baseSize*2
        case KDecoration2::BorderSize::Large:
            return baseSize*2; // Breeze: baseSize*3
        case KDecoration2::BorderSize::VeryLarge:
            return baseSize*3; // Breeze: ...
        case KDecoration2::BorderSize::Huge:
            return baseSize*4;
        case KDecoration2::BorderSize::VeryHuge:
            return baseSize*5;
        case KDecoration2::BorderSize::Oversized:
            return baseSize*10; // Same as Breeze
    }
}
int Decoration::sideBorderSize() const {
    switch (settings()->borderSize()) {
        case KDecoration2::BorderSize::NoSides:
            return 0;
        default:
            return bottomBorderSize();
    }
}

bool Decoration::Decoration::leftBorderVisible() const {
    const auto *decoratedClient = client().toStrongRef().data();
    return !decoratedClient->isMaximizedHorizontally()
        && !decoratedClient->adjacentScreenEdges().testFlag(Qt::LeftEdge);
}
bool Decoration::rightBorderVisible() const {
    const auto *decoratedClient = client().toStrongRef().data();
    return !decoratedClient->isMaximizedHorizontally()
        && !decoratedClient->adjacentScreenEdges().testFlag(Qt::RightEdge);
}
bool Decoration::topBorderVisible() const {
    const auto *decoratedClient = client().toStrongRef().data();
    return !decoratedClient->isMaximizedVertically()
        && !decoratedClient->adjacentScreenEdges().testFlag(Qt::TopEdge);
}
bool Decoration::bottomBorderVisible() const {
    const auto *decoratedClient = client().toStrongRef().data();
    return !decoratedClient->isMaximizedVertically()
        && !decoratedClient->adjacentScreenEdges().testFlag(Qt::BottomEdge)
        && !decoratedClient->isShaded();
}

bool Decoration::titleBarIsHovered() const
{
    return sectionUnderMouse() == Qt::TitleBarArea;
}

int Decoration::getTextWidth(const QString text, bool showMnemonic) const
{
    const QFontMetrics fontMetrics(settings()->font());
    const QRect textRect(titleBarRect());
    int flags = showMnemonic ? Qt::TextShowMnemonic : Qt::TextHideMnemonic;
    const QRect boundingRect = fontMetrics.boundingRect(textRect, flags, text);
    return boundingRect.width();
}

//* scoped pointer convenience typedef
template <typename T> using ScopedPointer = QScopedPointer<T, QScopedPointerPodDeleter>;

QPoint Decoration::windowPos() const
{
    const auto *decoratedClient = client().toStrongRef().data();
    WId windowId = decoratedClient->windowId();

    if (KWindowSystem::isPlatformX11()) {
#if HAVE_X11
        //--- From: BreezeSizeGrip.cpp
        /*
        get root position matching position
        need to use xcb because the embedding of the widget
        breaks QT's mapToGlobal and other methods
        */
        auto connection( QX11Info::connection() );
        xcb_get_geometry_cookie_t cookie( xcb_get_geometry( connection, windowId ) );
        ScopedPointer<xcb_get_geometry_reply_t> reply( xcb_get_geometry_reply( connection, cookie, nullptr ) );
        if (reply) {
            // translate coordinates
            xcb_translate_coordinates_cookie_t coordCookie( xcb_translate_coordinates(
                connection, windowId, reply.data()->root,
                -reply.data()->border_width,
                -reply.data()->border_width ) );

            ScopedPointer< xcb_translate_coordinates_reply_t> coordReply( xcb_translate_coordinates_reply( connection, coordCookie, nullptr ) );

            if (coordReply) {
                return QPoint(coordReply.data()->dst_x, coordReply.data()->dst_y);
            }
        }
#else
        Q_UNUSED(windowId)
#endif

    } else if (KWindowSystem::isPlatformWayland()) {
#if HAVE_Wayland
        // TODO
#endif
    }

    return QPoint(0, 0);
}

void Decoration::initDragMove(const QPoint pos)
{
    m_pressedPoint = pos;
}

void Decoration::resetDragMove()
{
    m_pressedPoint = QPoint();
}


bool Decoration::dragMoveTick(const QPoint pos)
{
    if (m_pressedPoint.isNull()) {
        return false;
    }

    QPoint diff = pos - m_pressedPoint;
    // qCDebug(category) << "    diff" << diff << "mL" << diff.manhattanLength() << "sDD" << QApplication::startDragDistance();
    if (diff.manhattanLength() >= QApplication::startDragDistance()) {
        sendMoveEvent(pos);
        resetDragMove();
        return true;
    }
    return false;
}

void Decoration::sendMoveEvent(const QPoint pos)
{
    const auto *decoratedClient = client().toStrongRef().data();
    WId windowId = decoratedClient->windowId();

    QPoint globalPos = windowPos()
        - QPoint(0, titleBarHeight())
        + pos;

    if (KWindowSystem::isPlatformX11()) {
#if HAVE_X11
        //--- From: BreezeSizeGrip.cpp
        auto connection(QX11Info::connection());


        // move/resize atom
        if (!m_moveResizeAtom) {
            // create atom if not found
            const QString atomName( "_NET_WM_MOVERESIZE" );
            xcb_intern_atom_cookie_t cookie( xcb_intern_atom( connection, false, atomName.size(), qPrintable( atomName ) ) );
            ScopedPointer<xcb_intern_atom_reply_t> reply( xcb_intern_atom_reply( connection, cookie, nullptr ) );
            m_moveResizeAtom = reply ? reply->atom : 0;
        }
        if (!m_moveResizeAtom) {
            return;
        }

        // button release event
        xcb_button_release_event_t releaseEvent;
        memset(&releaseEvent, 0, sizeof(releaseEvent));

        releaseEvent.response_type = XCB_BUTTON_RELEASE;
        releaseEvent.event =  windowId;
        releaseEvent.child = XCB_WINDOW_NONE;
        releaseEvent.root = QX11Info::appRootWindow();
        releaseEvent.event_x = pos.x();
        releaseEvent.event_y = pos.y();
        releaseEvent.root_x = globalPos.x();
        releaseEvent.root_y = globalPos.y();
        releaseEvent.detail = XCB_BUTTON_INDEX_1;
        releaseEvent.state = XCB_BUTTON_MASK_1;
        releaseEvent.time = XCB_CURRENT_TIME;
        releaseEvent.same_screen = true;
        xcb_send_event(
            connection,
            false,
            windowId,
            XCB_EVENT_MASK_BUTTON_RELEASE,
            reinterpret_cast<const char*>(&releaseEvent)
        );

        xcb_ungrab_pointer(connection, XCB_TIME_CURRENT_TIME);

        // move resize event
        xcb_client_message_event_t clientMessageEvent;
        memset(&clientMessageEvent, 0, sizeof(clientMessageEvent));

        clientMessageEvent.response_type = XCB_CLIENT_MESSAGE;
        clientMessageEvent.type = m_moveResizeAtom;
        clientMessageEvent.format = 32;
        clientMessageEvent.window = windowId;
        clientMessageEvent.data.data32[0] = globalPos.x();
        clientMessageEvent.data.data32[1] = globalPos.y();
        clientMessageEvent.data.data32[2] = 8; // _NET_WM_MOVERESIZE_MOVE
        clientMessageEvent.data.data32[3] = Qt::LeftButton;
        clientMessageEvent.data.data32[4] = 0;

        xcb_send_event(
            connection,
            false,
            QX11Info::appRootWindow(),
            XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
            reinterpret_cast<const char*>(&clientMessageEvent)
        );

        xcb_flush(connection);
#else
        Q_UNUSED(windowId)
        Q_UNUSED(globalPos)
#endif

    } else if (KWindowSystem::isPlatformWayland()) {
#if HAVE_Wayland
        // TODO
#endif

    } else {
        // Not X11
    }
}

void Decoration::paintFrameBackground(QPainter *painter, const QRect &repaintRegion) const
{
    Q_UNUSED(repaintRegion)

    painter->save();

    painter->fillRect(rect(), Qt::transparent);
    painter->setRenderHint(QPainter::Antialiasing);
    painter->setPen(Qt::NoPen);
    painter->setBrush(borderColor());
    painter->setClipRect(0, borderTop(), size().width(), size().height() - borderTop(), Qt::IntersectClip);
    painter->drawRect(rect());

    painter->restore();
}

QColor Decoration::borderColor() const
{
    const auto *decoratedClient = client().toStrongRef().data();
    const auto group = decoratedClient->isActive()
        ? KDecoration2::ColorGroup::Active
        : KDecoration2::ColorGroup::Inactive;
    const qreal opacity = decoratedClient->isActive()
        ? m_internalSettings->activeOpacity()
        : m_internalSettings->inactiveOpacity();
    QColor color = decoratedClient->color(group, KDecoration2::ColorRole::Frame);
    color.setAlphaF(opacity);
    return color;
}

QColor Decoration::titleBarBackgroundColor() const
{
    const auto *decoratedClient = client().toStrongRef().data();
    const auto group = decoratedClient->isActive()
        ? KDecoration2::ColorGroup::Active
        : KDecoration2::ColorGroup::Inactive;
    const qreal opacity = decoratedClient->isActive()
        ? m_internalSettings->activeOpacity()
        : m_internalSettings->inactiveOpacity();
    QColor color = decoratedClient->color(group, KDecoration2::ColorRole::TitleBar);
    color.setAlphaF(opacity);
    return color;
}

QColor Decoration::titleBarForegroundColor() const
{
    const auto *decoratedClient = client().toStrongRef().data();
    const auto group = decoratedClient->isActive()
        ? KDecoration2::ColorGroup::Active
        : KDecoration2::ColorGroup::Inactive;
    return decoratedClient->color(group, KDecoration2::ColorRole::Foreground);
}

void Decoration::paintTitleBarBackground(QPainter *painter, const QRect &repaintRegion) const
{
    Q_UNUSED(repaintRegion)

    painter->save();
    painter->setPen(Qt::NoPen);
    painter->setBrush(titleBarBackgroundColor());
    painter->drawRect(QRect(0, 0, size().width(), titleBarHeight()));
    painter->restore();
}

void Decoration::paintCaption(QPainter *painter, const QRect &repaintRegion) const
{
    Q_UNUSED(repaintRegion)

    if (m_internalSettings->titleAlignment() == InternalSettings::TitleHidden) {
        return;
    }

    const auto *decoratedClient = client().toStrongRef().data();

    const int textWidth = settings()->fontMetrics().boundingRect(decoratedClient->caption()).width();
    const QRect textRect((size().width() - textWidth) / 2, 0, textWidth, titleBarHeight());

    const bool appMenuVisible = !m_menuButtons->buttons().isEmpty();
    const int menuButtonsWidth = m_menuButtons->geometry().width()
        + (appMenuVisible ? appMenuCaptionSpacing() : 0);

    const QRect availableRect = centerRect().adjusted(
        (m_menuButtons->alwaysShow() ? menuButtonsWidth : 0),
        0,
        0,
        0
    );

    QRect captionRect;
    Qt::Alignment alignment;

    switch (m_internalSettings->titleAlignment()) {
        case InternalSettings::AlignLeft:
            captionRect = availableRect;
            alignment = Qt::AlignLeft | Qt::AlignVCenter;
            break;

        case InternalSettings::AlignRight:
            captionRect = availableRect;
            alignment = Qt::AlignRight | Qt::AlignVCenter;
            break;

        case InternalSettings::AlignCenter:
            captionRect = availableRect;
            alignment = Qt::AlignCenter;
            break;

        default:
        case InternalSettings::AlignCenterFullWidth:
            if (textRect.left() < availableRect.left()) {
                captionRect = availableRect;
                alignment = Qt::AlignLeft | Qt::AlignVCenter;
            } else if (availableRect.right() < textRect.right()) {
                captionRect = availableRect;
                alignment = Qt::AlignRight | Qt::AlignVCenter;
            } else {
                captionRect = titleBarRect();
                alignment = Qt::AlignCenter;
            }
            break;
    }

    const QString caption = painter->fontMetrics().elidedText(
        decoratedClient->caption(), Qt::ElideMiddle, captionRect.width());

    painter->save();
    painter->setFont(settings()->font());

    if (m_menuButtons->buttons().isEmpty()) {
        painter->setPen(titleBarForegroundColor());
    } else { // menuButtons is visible
        const int menuRight = m_menuButtons->geometry().right();
        const int textLeft = textRect.left();
        const int textRight = textRect.right();
        // qCDebug(category) << "textLeft" << textLeft << "menuRight" << menuRight;

        if (!m_menuButtons->alwaysShow()) { // caption fades away revealing menu
            painter->setOpacity(1.0 - m_menuButtons->opacity());
            painter->setPen(titleBarForegroundColor());
        } else if (m_menuButtons->overflowing()) { // hide caption leaving "whitespace" to easily grab.
            painter->setPen(Qt::transparent);
        } else if (textRight < menuRight) { // menuButtons completely coveres caption
            painter->setPen(Qt::transparent);
        } else if (textLeft < menuRight) { // menuButtons covers caption
            const int fadeWidth = 10; // TODO: scale by dpi
            const int x1 = menuRight;
            const int x2 = qMin(x1+fadeWidth, textRight);
            const float x1Ratio = (float)(x1-textLeft) / (float)textWidth;
            const float x2Ratio = (float)(x2-textLeft) / (float)textWidth;
            // qCDebug(category) << "    " << "x2" << x2 << "x1R" << x1Ratio << "x2R" << x2Ratio;
            QLinearGradient gradient(textRect.topLeft(), textRect.bottomRight());
            gradient.setColorAt(x1Ratio, Qt::transparent);
            gradient.setColorAt(x2Ratio, titleBarForegroundColor());
            QBrush brush(gradient);
            QPen pen(brush, 1);
            painter->setPen(pen);
        } else { // caption is not covered by menuButtons
            painter->setPen(titleBarForegroundColor());
        }
    }

    painter->drawText(captionRect, alignment, caption);
    painter->restore();
}

void Decoration::paintButtons(QPainter *painter, const QRect &repaintRegion) const
{
    m_leftButtons->paint(painter, repaintRegion);
    m_rightButtons->paint(painter, repaintRegion);
    m_menuButtons->paint(painter, repaintRegion);
}

void Decoration::paintOutline(QPainter *painter, const QRect &repaintRegion) const
{
    Q_UNUSED(repaintRegion)

    // Simple 1px border outline
    painter->save();
    painter->setRenderHint(QPainter::Antialiasing, false);
    painter->setBrush(Qt::NoBrush);
    QColor outlineColor(titleBarForegroundColor());
    outlineColor.setAlphaF(0.25);
    painter->setPen(outlineColor);
    painter->drawRect( rect().adjusted( 0, 0, -1, -1 ) );
    painter->restore();
}

} // namespace Material


================================================
FILE: src/Decoration.h
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// own
#include "BuildConfig.h"
#include "AppMenuButtonGroup.h"
#include "InternalSettings.h"

// KDecoration
#include <KDecoration2/Decoration>
#include <KDecoration2/DecorationButton>
#include <KDecoration2/DecorationButtonGroup>

// Qt
#include <QHoverEvent>
#include <QMouseEvent>
#include <QRectF>
#include <QSharedPointer>
#include <QWheelEvent>
#include <QVariant>

#if HAVE_X11
#include <xcb/xcb.h>
#endif


namespace Material
{

class Button;
class TextButton;
class MenuOverflowButton;

class Decoration : public KDecoration2::Decoration
{
    Q_OBJECT

public:
    Decoration(QObject *parent = nullptr, const QVariantList &args = QVariantList());
    ~Decoration() override;

    QRect titleBarRect() const;
    QRect centerRect() const;

    void paint(QPainter *painter, const QRect &repaintRegion) override;

public slots:
    void init() override;
    void reconfigure();

protected:
    void hoverEnterEvent(QHoverEvent *event) override;
    void hoverMoveEvent(QHoverEvent *event) override;
    void hoverLeaveEvent(QHoverEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void wheelEvent(QWheelEvent *event) override;

private slots:
    void onSectionUnderMouseChanged(const Qt::WindowFrameSection value);

private:
    void updateBlur();
    void updateBorders();
    void updateResizeBorders();
    void updateTitleBar();
    void updateTitleBarHoverState();
    void setButtonGroupHeight(KDecoration2::DecorationButtonGroup *buttonGroup, int buttonHeight);
    void setButtonGroupHorzPadding(KDecoration2::DecorationButtonGroup *buttonGroup, int value);
    void setButtonGroupVertPadding(KDecoration2::DecorationButtonGroup *buttonGroup, int value);
    void updateButtonHeight();
    void updateButtonsGeometry();
    void setButtonGroupAnimation(KDecoration2::DecorationButtonGroup *buttonGroup, bool enabled, int duration);
    void updateButtonAnimation();
    void updateShadow();

    bool menuAlwaysShow() const;
    bool animationsEnabled() const;
    int animationsDuration() const;
    int buttonPadding() const;
    int titleBarHeight() const;
    int appMenuButtonHorzPadding() const;
    int appMenuCaptionSpacing() const;
    int captionMinWidth() const;

    int bottomBorderSize() const;
    int sideBorderSize() const;

    bool leftBorderVisible() const;
    bool rightBorderVisible() const;
    bool topBorderVisible() const;
    bool bottomBorderVisible() const;

    bool titleBarIsHovered() const;
    int getTextWidth(const QString text, bool showMnemonic = false) const;
    QPoint windowPos() const;

    void initDragMove(const QPoint pos);
    void resetDragMove();
    bool dragMoveTick(const QPoint pos);
    void sendMoveEvent(const QPoint pos);

    QColor borderColor() const;
    QColor titleBarBackgroundColor() const;
    QColor titleBarForegroundColor() const;

    void paintFrameBackground(QPainter *painter, const QRect &repaintRegion) const;
    void paintTitleBarBackground(QPainter *painter, const QRect &repaintRegion) const;
    void paintCaption(QPainter *painter, const QRect &repaintRegion) const;
    void paintButtons(QPainter *painter, const QRect &repaintRegion) const;
    void paintOutline(QPainter *painter, const QRect &repaintRegion) const;

    KDecoration2::DecorationButtonGroup *m_leftButtons;
    KDecoration2::DecorationButtonGroup *m_rightButtons;
    AppMenuButtonGroup *m_menuButtons;

    QSharedPointer<InternalSettings> m_internalSettings;

    QPoint m_pressedPoint;

#if HAVE_X11
    xcb_atom_t m_moveResizeAtom = 0;
#endif

    friend class AppMenuButtonGroup;
    friend class Button;
    friend class AppIconButton;
    friend class AppMenuButton;
    friend class TextButton;
    // friend class MenuOverflowButton;
};

} // namespace Material


================================================
FILE: src/InternalSettings.kcfgc
================================================
File=InternalSettingsSchema.kcfg
ClassName=InternalSettings
NameSpace=Material
Singleton=false
Mutators=true
GlobalEnums=true


================================================
FILE: src/InternalSettingsSchema.kcfg
================================================
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
    <kcfgfile name="kdecoration_materialrc"/>

    <group name="Windeco">

        <!-- button size -->
        <entry name="ButtonSize" type="Enum">
            <choices>
                <choice name="ButtonTiny" />
                <choice name="ButtonSmall" />
                <choice name="ButtonDefault" />
                <choice name="ButtonLarge" />
                <choice name="ButtonVeryLarge" />
            </choices>
            <default>ButtonDefault</default>
        </entry>

        <!-- title alignment -->
        <entry name="TitleAlignment" type="Enum">
            <choices>
                <choice name="AlignLeft" />
                <choice name="AlignCenter" />
                <choice name="AlignCenterFullWidth" />
                <choice name="AlignRight" />
                <choice name="TitleHidden" />
            </choices>
            <default>AlignCenterFullWidth</default>
        </entry>

        <!-- opacity -->
        <entry name="ActiveOpacity" type="Double">
            <default>0.75</default>
        </entry>
        <entry name="InactiveOpacity" type="Double">
            <default>0.85</default>
        </entry>

        <!-- menu -->
        <entry name="MenuAlwaysShow" type="Bool">
            <default>true</default>
        </entry>
        <entry name="MenuButtonHorzPadding" type="Int">
            <default>4</default>
        </entry>

        <!-- animations -->
        <entry name="AnimationsEnabled" type="Bool">
            <default>true</default>
        </entry>
        <entry name="AnimationsDuration" type="Int">
            <default>250</default>
        </entry>

        <!-- shadow -->
        <entry name="ShadowSize" type="Enum">
            <choices>
                <choice name="ShadowNone"/>
                <choice name="ShadowSmall"/>
                <choice name="ShadowMedium"/>
                <choice name="ShadowLarge"/>
                <choice name="ShadowVeryLarge"/>
            </choices>
            <default>ShadowVeryLarge</default>
        </entry>
        <entry name="ShadowColor" type="Color">
            <default>33, 33, 33</default>
        </entry>
        <entry name="ShadowStrength" type="Int">
            <default>255</default>
            <min>25</min>
            <max>255</max>
        </entry>
    </group>

</kcfg>


================================================
FILE: src/KeepAboveButton.h
================================================
/*
 * Copyright (C) 2019 Zain Ahmad <zain.x.ahmad@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// own
#include "Button.h"
#include "Material.h"

// KDecoration
#include <KDecoration2/DecoratedClient>

// Qt
#include <QPainter>

namespace Material
{

class KeepAboveButton
{

public:
    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {
        Q_UNUSED(decoratedClient)

        button->setVisible(true);
    }
    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {
        button->setPenWidth(painter, gridUnit, 1.25);

        painter->translate( iconRect.topLeft() );
        painter->drawPolyline(  QVector<QPointF> {
            QPointF( 0.5, 4.75 ) * gridUnit,
            QPointF( 5.0, 0.25 ) * gridUnit,
            QPointF( 9.5, 4.75 ) * gridUnit
        });

        painter->drawPolyline(  QVector<QPointF> {
            QPointF( 0.5, 9.75 ) * gridUnit,
            QPointF( 5.0, 5.25 ) * gridUnit,
            QPointF( 9.5, 9.75 ) * gridUnit
        });
    }
};

} // namespace Material


================================================
FILE: src/KeepBelowButton.h
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// own
#include "Button.h"
#include "Material.h"

// KDecoration
#include <KDecoration2/DecoratedClient>

// Qt
#include <QPainter>

namespace Material
{

class KeepBelowButton
{

public:
    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {
        Q_UNUSED(decoratedClient)

        button->setVisible(true);
    }
    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {
        button->setPenWidth(painter, gridUnit, 1.25);

        painter->translate( iconRect.topLeft() );
        painter->drawPolyline(  QVector<QPointF> {
            QPointF( 0.5, 0.25 ) * gridUnit,
            QPointF( 5.0, 4.75 ) * gridUnit,
            QPointF( 9.5, 0.25 ) * gridUnit
        });

        painter->drawPolyline(  QVector<QPointF> {
            QPointF( 0.5, 5.25 ) * gridUnit,
            QPointF( 5.0, 9.75 ) * gridUnit,
            QPointF( 9.5, 5.25 ) * gridUnit
        });
    }
};

} // namespace Material


================================================
FILE: src/Material.h
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// Qt
#include <QLoggingCategory>


namespace Material
{
    static const QLoggingCategory category("kdecoration.material");
    static const QString s_configFilename = QStringLiteral("kdecoration_materialrc");

    //--- Standard pen widths
    namespace PenWidth
    {
        /* https://github.com/KDE/breeze/blob/master/kstyle/breeze.h#L164
         * Using 1 instead of slightly more than 1 causes symbols drawn with
         * pen strokes to look skewed. The exact amount added does not matter
         * as long as it isn't too visible.
         */
        // The standard pen stroke width for symbols.
        static constexpr qreal Symbol = 1.01;
    }

} // namespace Material


================================================
FILE: src/MaximizeButton.h
================================================
/*
 * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// own
#include "Button.h"

// KDecoration
#include <KDecoration2/DecoratedClient>

// Qt
#include <QPainter>

namespace Material
{

class MaximizeButton
{

public:
    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {
        QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::maximizeableChanged,
                button, &Button::setVisible);

        button->setVisible(decoratedClient->isMaximizeable());
    }
    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, qreal gridUnit) {
        if (button->isChecked()) {
            const int offset = qRound(gridUnit * 2);
            // Outline of first square, "on top", aligned bottom left.
            painter->drawPolygon(QVector<QPointF> {
                iconRect.bottomLeft(),
                iconRect.topLeft() + QPointF(0, offset),
                iconRect.topRight() + QPointF(-offset, offset),
                iconRect.bottomRight() + QPointF(-offset, 0)
            });

            // Partially occluded square, "below" first square, aligned top right.
            painter->drawPolyline(QVector<QPointF> {
                iconRect.topLeft() + QPointF(offset, offset),
                iconRect.topLeft() + QPointF(offset, 0),
                iconRect.topRight(),
                iconRect.bottomRight() + QPointF(0, -offset),
                iconRect.bottomRight() + QPointF(-offset, -offset)
            });
        } else {
            painter->drawRect(iconRect);
        }
    }
};

} // namespace Material


================================================
FILE: src/MenuOverflowButton.cc
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 * Copyright (C) 2019 Zain Ahmad <zain.x.ahmad@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

// own
#include "MenuOverflowButton.h"
#include "Material.h"
#include "AppMenuButton.h"
#include "Decoration.h"
#include "ApplicationMenuButton.h"

// KDecoration
#include <KDecoration2/DecoratedClient>

// Qt
#include <QDebug>
#include <QPainter>


namespace Material
{

MenuOverflowButton::MenuOverflowButton(Decoration *decoration, const int buttonIndex, QObject *parent)
    : AppMenuButton(decoration, buttonIndex, parent)
{
    auto *decoratedClient = decoration->client().toStrongRef().data();

    setVisible(decoratedClient->hasApplicationMenu());
}

MenuOverflowButton::~MenuOverflowButton()
{
}

void MenuOverflowButton::paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit)
{
    ApplicationMenuButton::paintIcon(this, painter, iconRect, gridUnit);
}

} // namespace Material


================================================
FILE: src/MenuOverflowButton.h
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 * Copyright (C) 2019 Zain Ahmad <zain.x.ahmad@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// own
#include "AppMenuButton.h"

namespace Material
{

class Decoration;

class MenuOverflowButton : public AppMenuButton
{
    Q_OBJECT

public:
    MenuOverflowButton(Decoration *decoration, const int buttonIndex, QObject *parent = nullptr);
    ~MenuOverflowButton() override;

    void paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit) override;
};

} // namespace Material


================================================
FILE: src/MinimizeButton.h
================================================
/*
 * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// own
#include "Button.h"

// KDecoration
#include <KDecoration2/DecoratedClient>

// Qt
#include <QPainter>

namespace Material
{

class MinimizeButton
{

public:
    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {
        QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::minimizeableChanged,
                button, &Button::setVisible);

        button->setVisible(decoratedClient->isMinimizeable());
    }
    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {
        Q_UNUSED(button)
        Q_UNUSED(gridUnit)

        painter->drawLine(
            iconRect.left(), iconRect.center().y(),
            iconRect.right(), iconRect.center().y());
    }
};

} // namespace Material


================================================
FILE: src/OnAllDesktopsButton.h
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// own
#include "Button.h"
#include "Material.h"

// KDecoration
#include <KDecoration2/DecoratedClient>

// Qt
#include <QPainter>

namespace Material
{

class OnAllDesktopsButton
{

public:
    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {
        Q_UNUSED(decoratedClient)

        button->setVisible(true);
    }
    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {
        Q_UNUSED(gridUnit)
        painter->setRenderHints(QPainter::Antialiasing, true);
        button->setPenWidth(painter, gridUnit, 1.25);

        int radius = qMin(iconRect.width(), iconRect.height()) / 2;
        QPoint center(iconRect.center().toPoint());
        painter->drawPolygon( QVector<QPointF> {
            center + QPoint(-radius, 0),
            center + QPoint(0, -radius),
            center + QPoint(radius, 0),
            center + QPoint(0, radius)
        });
    }
};

} // namespace Material


================================================
FILE: src/ShadeButton.h
================================================
/*
 * Copyright (C) 2021 Chris Holland <zrenfire@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// own
#include "Button.h"
#include "Material.h"

// KDecoration
#include <KDecoration2/DecoratedClient>

// Qt
#include <QPainter>

namespace Material
{

class ShadeButton
{

public:
    static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) {
        QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::shadeableChanged,
                button, &Button::setVisible);

        button->setVisible(decoratedClient->isShadeable());
    }
    static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) {
        painter->translate( iconRect.topLeft() );

        if (button->isChecked()) {
            button->setPenWidth(painter, gridUnit, 1.0);
            painter->drawLine( 
                QPointF( 0, 2 ) * gridUnit,
                QPointF( 10, 2 ) * gridUnit
            );
            button->setPenWidth(painter, gridUnit, 1.25);
            painter->drawPolyline(  QVector<QPointF> {
                QPointF( 0.5, 5.25 ) * gridUnit,
                QPointF( 5.0, 9.75 ) * gridUnit,
                QPointF( 9.5, 5.25 ) * gridUnit
            });
        } else {
            button->setPenWidth(painter, gridUnit, 1.0);
            painter->drawLine( 
                QPointF( 0, 2 ) * gridUnit,
                QPointF( 10, 2 ) * gridUnit
            );
            button->setPenWidth(painter, gridUnit, 1.25);
            painter->drawPolyline( QVector<QPointF> {
                QPointF( 0.5, 9.75 ) * gridUnit,
                QPointF( 5.0, 5.25 ) * gridUnit,
                QPointF( 9.5, 9.75 ) * gridUnit
            });
        }
    }
};

} // namespace Material


================================================
FILE: src/TextButton.cc
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 * Copyright (C) 2016 Kai Uwe Broulik <kde@privat.broulik.de>
 * Copyright (C) 2014 by Hugo Pereira Da Costa <hugo.pereira@free.fr>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

// own
#include "TextButton.h"
#include "Material.h"
#include "AppMenuButton.h"
#include "Decoration.h"

// KDecoration
#include <KDecoration2/DecoratedClient>
#include <KDecoration2/DecorationSettings>

// Qt
#include <QDebug>
#include <QAction>
#include <QFontMetrics>
#include <QPainter>


namespace Material
{

TextButton::TextButton(Decoration *decoration, const int buttonIndex, QObject *parent)
    : AppMenuButton(decoration, buttonIndex, parent)
    , m_action(nullptr)
    , m_text(QStringLiteral("Menu"))
{
    const auto *deco = qobject_cast<Decoration *>(decoration);

    setHorzPadding(deco->appMenuButtonHorzPadding());

    setVisible(true);
}

TextButton::~TextButton()
{
}

void TextButton::paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit)
{
    Q_UNUSED(iconRect)
    Q_UNUSED(gridUnit)

    // Font
    painter->setFont(decoration()->settings()->font());

    // TODO: Use Qt::TextShowMnemonic when Alt is pressed
    const bool isAltPressed = false;
    const Qt::TextFlag mnemonicFlag = isAltPressed ? Qt::TextShowMnemonic : Qt::TextHideMnemonic;
    painter->drawText(geometry(), mnemonicFlag | Qt::AlignCenter, m_text);
}

QSize TextButton::getTextSize()
{
    const auto *deco = qobject_cast<Decoration *>(decoration());
    if (!deco) {
        return QSize(0, 0);
    }

    // const QString elidedText = painter->fontMetrics().elidedText(
    //     m_text,
    //     Qt::ElideRight,
    //     100, // Max width TODO: scale by dpi
    // );
    const int textWidth = deco->getTextWidth(m_text);
    const int titleBarHeight = deco->titleBarHeight();
    const QSize size(textWidth, titleBarHeight);
    return size;
}

QAction* TextButton::action() const
{
    return m_action;
}

void TextButton::setAction(QAction *set)
{
    if (m_action != set) {
        m_action = set;
        emit actionChanged();
    }
}

QString TextButton::text() const
{
    return m_text;
}

void TextButton::setText(const QString set)
{
    if (m_text != set) {
        m_text = set;
        emit textChanged();

        updateGeometry();
    }
}

void TextButton::setHeight(int buttonHeight)
{
    Q_UNUSED(buttonHeight)

    updateGeometry();
}

void TextButton::updateGeometry()
{
    const QSize textSize = getTextSize();
    updateSize(textSize.width(), textSize.height());
}


} // namespace Material


================================================
FILE: src/TextButton.h
================================================
/*
 * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// own
#include "AppMenuButton.h"

// Qt
#include <QAction>

namespace Material
{

class Decoration;

class TextButton : public AppMenuButton
{
    Q_OBJECT

public:
    TextButton(Decoration *decoration, const int buttonIndex, QObject *parent = nullptr);
    ~TextButton() override;

    Q_PROPERTY(QAction* action READ action WRITE setAction NOTIFY actionChanged)
    Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)

    void paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit) override;
    QSize getTextSize();


    QAction* action() const;
    void setAction(QAction *set);

    QString text() const;
    void setText(const QString set);

    void setHeight(int buttonHeight) override;
    void updateGeometry();

signals:
    void actionChanged();
    void textChanged();

private:
    QAction *m_action;
    QString m_text;
};

} // namespace Material


================================================
FILE: src/libdbusmenuqt/CMakeLists.txt
================================================
set(libdbusmenu_SRCS
    dbusmenuimporter.cpp
    dbusmenushortcut_p.cpp
    dbusmenutypes_p.cpp
    utils.cpp
)

ecm_qt_declare_logging_category(libdbusmenu_SRCS
    HEADER debug.h
    IDENTIFIER DBUSMENUQT
    CATEGORY_NAME org.kde.libdbusmenuqt
    DEFAULT_SEVERITY Info
)

set_source_files_properties(com.canonical.dbusmenu.xml PROPERTIES
    NO_NAMESPACE true
    INCLUDE "dbusmenutypes_p.h"
    CLASSNAME DBusMenuInterface
)

if (Qt6_FOUND)
    qt_add_dbus_interface(libdbusmenu_SRCS com.canonical.dbusmenu.xml dbusmenu_interface)
elseif(Qt5_FOUND)
    qt5_add_dbus_interface(libdbusmenu_SRCS com.canonical.dbusmenu.xml dbusmenu_interface)
endif()



add_library(dbusmenuqt STATIC ${libdbusmenu_SRCS})
target_link_libraries(dbusmenuqt
    Qt${QT_VERSION_MAJOR}::DBus
    Qt${QT_VERSION_MAJOR}::Widgets
)

add_subdirectory(test)


================================================
FILE: src/libdbusmenuqt/README
================================================
Contains a patched version of the import path of libdbusmenu-qt
Remove when next version of libdbusmenu-qt is released.

================================================
FILE: src/libdbusmenuqt/com.canonical.dbusmenu.xml
================================================
<interface name="com.canonical.dbusmenu">
    <property name="Version" type="u" access="read"/>
    <property name="Status" type="s" access="read"/>
    <signal name="ItemsPropertiesUpdated">
    <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="DBusMenuItemList"/>
    <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="DBusMenuItemKeysList"/>
    <arg type="a(ia{sv})" direction="out"/>
    <arg type="a(ias)" direction="out"/>
    </signal>
    <signal name="LayoutUpdated">
    <arg name="revision" type="u" direction="out"/>
    <arg name="parentId" type="i" direction="out"/>
    </signal>
    <signal name="ItemActivationRequested">
    <arg name="id" type="i" direction="out"/>
    <arg name="timeStamp" type="u" direction="out"/>
    </signal>
    <method name="Event">
    <arg name="id" type="i" direction="in"/>
    <arg name="eventId" type="s" direction="in"/>
    <arg name="data" type="v" direction="in"/>
    <arg name="timestamp" type="u" direction="in"/>
    <annotation name="org.freedesktop.DBus.Method.NoReply" value="true"/>
    </method>
    <method name="GetProperty">
    <arg type="v" direction="out"/>
    <arg name="id" type="i" direction="in"/>
    <arg name="property" type="s" direction="in"/>
    </method>
    <method name="GetLayout">
    <arg type="u" direction="out"/>
    <arg name="parentId" type="i" direction="in"/>
    <arg name="recursionDepth" type="i" direction="in"/>
    <arg name="propertyNames" type="as" direction="in"/>
    <arg name="item" type="(ia{sv}av)" direction="out"/>
    <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="DBusMenuLayoutItem"/>
    </method>
    <method name="GetGroupProperties">
    <arg type="a(ia{sv})" direction="out"/>
    <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="DBusMenuItemList"/>
    <arg name="ids" type="ai" direction="in"/>
    <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QList&lt;int&gt;"/>
    <arg name="propertyNames" type="as" direction="in"/>
    </method>
    <method name="AboutToShow">
    <arg type="b" direction="out"/>
    <arg name="id" type="i" direction="in"/>
    </method>
</interface>


================================================
FILE: src/libdbusmenuqt/dbusmenuimporter.cpp
================================================
/* This file is part of the dbusmenu-qt library
   Copyright 2009 Canonical
   Author: Aurelien Gateau <aurelien.gateau@canonical.com>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License (LGPL) as published by the Free Software Foundation;
   either version 2 of the License, or (at your option) any later
   version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/
#include "dbusmenuimporter.h"

#include "debug.h"

// Qt
#include <QCoreApplication>
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusReply>
#include <QDBusVariant>
#include <QDebug>
#include <QFont>
#include <QMenu>
#include <QPointer>
#include <QSet>
#include <QTime>
#include <QTimer>
#include <QToolButton>
#include <QWidgetAction>

// Local
#include "dbusmenushortcut_p.h"
#include "dbusmenutypes_p.h"
#include "utils_p.h"

// Generated
#include "dbusmenu_interface.h"

//#define BENCHMARK
#ifdef BENCHMARK
static QTime sChrono;
#endif

#define DMRETURN_IF_FAIL(cond)                                                                                                                                 \
    if (!(cond)) {                                                                                                                                             \
        qCWarning(DBUSMENUQT) << "Condition failed: " #cond;                                                                                                   \
        return;                                                                                                                                                \
    }

static const char *DBUSMENU_PROPERTY_ID = "_dbusmenu_id";
static const char *DBUSMENU_PROPERTY_ICON_NAME = "_dbusmenu_icon_name";
static const char *DBUSMENU_PROPERTY_ICON_DATA_HASH = "_dbusmenu_icon_data_hash";

static QAction *createKdeTitle(QAction *action, QWidget *parent)
{
    QToolButton *titleWidget = new QToolButton(nullptr);
    QFont font = titleWidget->font();
    font.setBold(true);
    titleWidget->setFont(font);
    titleWidget->setIcon(action->icon());
    titleWidget->setText(action->text());
    titleWidget->setDown(true);
    titleWidget->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);

    QWidgetAction *titleAction = new QWidgetAction(parent);
    titleAction->setDefaultWidget(titleWidget);
    return titleAction;
}

class DBusMenuImporterPrivate
{
public:
    DBusMenuImporter *q;

    DBusMenuInterface *m_interface;
    QMenu *m_menu;
    using ActionForId = QMap<int, QAction *>;
    ActionForId m_actionForId;
    QTimer *m_pendingLayoutUpdateTimer;

    QSet<int> m_idsRefreshedByAboutToShow;
    QSet<int> m_pendingLayoutUpdates;

    QDBusPendingCallWatcher *refresh(int id)
    {
        auto call = m_interface->GetLayout(id, 1, QStringList());
        QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, q);
        watcher->setProperty(DBUSMENU_PROPERTY_ID, id);
        QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q, &DBusMenuImporter::slotGetLayoutFinished);

        return watcher;
    }

    QMenu *createMenu(QWidget *parent)
    {
        QMenu *menu = q->createMenu(parent);
        return menu;
    }

    /**
     * Init all the immutable action properties here
     * TODO: Document immutable properties?
     *
     * Note: we remove properties we handle from the map (using QMap::take()
     * instead of QMap::value()) to avoid warnings about these properties in
     * updateAction()
     */
    QAction *createAction(int id, const QVariantMap &_map, QWidget *parent)
    {
        QVariantMap map = _map;
        QAction *action = new QAction(parent);
        action->setProperty(DBUSMENU_PROPERTY_ID, id);

        QString type = map.take(QStringLiteral("type")).toString();
        if (type == QLatin1String("separator")) {
            action->setSeparator(true);
        }

        if (map.take(QStringLiteral("children-display")).toString() == QLatin1String("submenu")) {
            QMenu *menu = createMenu(parent);
            action->setMenu(menu);
        }

        QString toggleType = map.take(QStringLiteral("toggle-type")).toString();
        if (!toggleType.isEmpty()) {
            action->setCheckable(true);
            if (toggleType == QLatin1String("radio")) {
                QActionGroup *group = new QActionGroup(action);
                group->addAction(action);
            }
        }

        bool isKdeTitle = map.take(QStringLiteral("x-kde-title")).toBool();
        updateAction(action, map, map.keys());

        if (isKdeTitle) {
            action = createKdeTitle(action, parent);
        }

        return action;
    }

    /**
     * Update mutable properties of an action. A property may be listed in
     * requestedProperties but not in map, this means we should use the default value
     * for this property.
     *
     * @param action the action to update
     * @param map holds the property values
     * @param requestedProperties which properties has been requested
     */
    void updateAction(QAction *action, const QVariantMap &map, const QStringList &requestedProperties)
    {
        Q_FOREACH (const QString &key, requestedProperties) {
            updateActionProperty(action, key, map.value(key));
        }
    }

    void updateActionProperty(QAction *action, const QString &key, const QVariant &value)
    {
        if (key == QLatin1String("label")) {
            updateActionLabel(action, value);
        } else if (key == QLatin1String("enabled")) {
            updateActionEnabled(action, value);
        } else if (key == QLatin1String("toggle-state")) {
            updateActionChecked(action, value);
        } else if (key == QLatin1String("icon-name")) {
            updateActionIconByName(action, value);
        } else if (key == QLatin1String("icon-data")) {
            updateActionIconByData(action, value);
        } else if (key == QLatin1String("visible")) {
            updateActionVisible(action, value);
        } else if (key == QLatin1String("shortcut")) {
            updateActionShortcut(action, value);
        } else {
            qDebug(DBUSMENUQT) << "Unhandled property update" << key;
        }
    }

    void updateActionLabel(QAction *action, const QVariant &value)
    {
        QString text = swapMnemonicChar(value.toString(), '_', '&');
        action->setText(text);
    }

    void updateActionEnabled(QAction *action, const QVariant &value)
    {
        action->setEnabled(value.isValid() ? value.toBool() : true);
    }

    void updateActionChecked(QAction *action, const QVariant &value)
    {
        if (action->isCheckable() && value.isValid()) {
            action->setChecked(value.toInt() == 1);
        }
    }

    void updateActionIconByName(QAction *action, const QVariant &value)
    {
        const QString iconName = value.toString();
        const QString previous = action->property(DBUSMENU_PROPERTY_ICON_NAME).toString();
        if (previous == iconName) {
            return;
        }
        action->setProperty(DBUSMENU_PROPERTY_ICON_NAME, iconName);
        if (iconName.isEmpty()) {
            action->setIcon(QIcon());
            return;
        }
        action->setIcon(q->iconForName(iconName));
    }

    void updateActionIconByData(QAction *action, const QVariant &value)
    {
        const QByteArray data = value.toByteArray();
        uint dataHash = qHash(data);
        uint previousDataHash = action->property(DBUSMENU_PROPERTY_ICON_DATA_HASH).toUInt();
        if (previousDataHash == dataHash) {
            return;
        }
        action->setProperty(DBUSMENU_PROPERTY_ICON_DATA_HASH, dataHash);
        QPixmap pix;
        if (!pix.loadFromData(data)) {
            qDebug(DBUSMENUQT) << "Failed to decode icon-data property for action" << action->text();
            action->setIcon(QIcon());
            return;
        }
        action->setIcon(QIcon(pix));
    }

    void updateActionVisible(QAction *action, const QVariant &value)
    {
        action->setVisible(value.isValid() ? value.toBool() : true);
    }

    void updateActionShortcut(QAction *action, const QVariant &value)
    {
        QDBusArgument arg = value.value<QDBusArgument>();
        DBusMenuShortcut dmShortcut;
        arg >> dmShortcut;
        QKeySequence keySequence = dmShortcut.toKeySequence();
        action->setShortcut(keySequence);
    }

    QMenu *menuForId(int id) const
    {
        if (id == 0) {
            return q->menu();
        }
        QAction *action = m_actionForId.value(id);
        if (!action) {
            return nullptr;
        }
        return action->menu();
    }

    void slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList);

    void sendEvent(int id, const QString &even
Download .txt
gitextract_y5m7lijt/

├── .clang-format
├── .gitignore
├── .vscode/
│   └── settings.json
├── CMakeLists.txt
├── LICENSE
├── README.md
└── src/
    ├── AppIconButton.h
    ├── AppMenuButton.cc
    ├── AppMenuButton.h
    ├── AppMenuButtonGroup.cc
    ├── AppMenuButtonGroup.h
    ├── AppMenuModel.cc
    ├── AppMenuModel.h
    ├── ApplicationMenuButton.h
    ├── BoxShadowHelper.cc
    ├── BoxShadowHelper.h
    ├── BuildConfig.h.cmake
    ├── Button.cc
    ├── Button.h
    ├── CMakeLists.txt
    ├── CloseButton.h
    ├── ConfigurationModule.cc
    ├── ConfigurationModule.h
    ├── ContextHelpButton.h
    ├── Decoration.cc
    ├── Decoration.h
    ├── InternalSettings.kcfgc
    ├── InternalSettingsSchema.kcfg
    ├── KeepAboveButton.h
    ├── KeepBelowButton.h
    ├── Material.h
    ├── MaximizeButton.h
    ├── MenuOverflowButton.cc
    ├── MenuOverflowButton.h
    ├── MinimizeButton.h
    ├── OnAllDesktopsButton.h
    ├── ShadeButton.h
    ├── TextButton.cc
    ├── TextButton.h
    ├── libdbusmenuqt/
    │   ├── CMakeLists.txt
    │   ├── README
    │   ├── com.canonical.dbusmenu.xml
    │   ├── dbusmenuimporter.cpp
    │   ├── dbusmenuimporter.h
    │   ├── dbusmenushortcut_p.cpp
    │   ├── dbusmenushortcut_p.h
    │   ├── dbusmenutypes_p.cpp
    │   ├── dbusmenutypes_p.h
    │   ├── test/
    │   │   ├── CMakeLists.txt
    │   │   ├── README
    │   │   └── main.cpp
    │   ├── utils.cpp
    │   └── utils_p.h
    ├── material.json
    └── plugin.cc
Download .txt
SYMBOL INDEX (115 symbols across 37 files)

FILE: src/AppIconButton.h
  function namespace (line 35) | namespace Material
  function paintIcon (line 49) | static void paintIcon(Button *button, QPainter *painter, const QRectF &i...

FILE: src/AppMenuButton.cc
  type Material (line 37) | namespace Material
    function QColor (line 64) | QColor AppMenuButton::backgroundColor() const
    function QColor (line 77) | QColor AppMenuButton::foregroundColor() const

FILE: src/AppMenuButton.h
  function namespace (line 23) | namespace Material

FILE: src/AppMenuButtonGroup.cc
  type Material (line 46) | namespace Material
    function qreal (line 197) | qreal AppMenuButtonGroup::opacity() const

FILE: src/AppMenuButtonGroup.h
  function namespace (line 32) | namespace Material

FILE: src/AppMenuModel.cc
  type Material (line 62) | namespace Material
    class KDBusMenuImporter (line 72) | class KDBusMenuImporter : public DBusMenuImporter
      method KDBusMenuImporter (line 76) | KDBusMenuImporter(const QString &service, const QString &path, QObje...
      method QIcon (line 82) | QIcon iconForName(const QString &name) override {
    function QVariant (line 185) | QVariant AppMenuModel::winId() const
    function QVariant (line 334) | QVariant AppMenuModel::data(const QModelIndex &index, int role) const

FILE: src/AppMenuModel.h
  function namespace (line 36) | namespace Material

FILE: src/ApplicationMenuButton.h
  function paintIcon (line 40) | static void paintIcon(Button *button, QPainter *painter, const QRectF &i...

FILE: src/BoxShadowHelper.cc
  type Material (line 28) | namespace Material
    type BoxShadowHelper (line 30) | namespace BoxShadowHelper
      function qreal (line 44) | inline qreal radiusToSigma(qreal radius)
      function boxSizeToRadius (line 49) | inline int boxSizeToRadius(int boxSize)
      function computeBoxSizes (line 54) | QVector<int> computeBoxSizes(int radius, int numIterations)
      function boxBlurPass (line 78) | void boxBlurPass(const QImage &src, QImage &dst, int boxSize)
      function boxBlurAlpha (line 128) | void boxBlurAlpha(QImage &image, int radius, int numIterations)
      function boxShadow (line 141) | void boxShadow(QPainter *p, const QRect &box, const QPoint &offset, ...

FILE: src/BoxShadowHelper.h
  function namespace (line 26) | namespace Material

FILE: src/Button.cc
  type Material (line 51) | namespace Material
    function qreal (line 303) | qreal Button::iconLineWidth(const qreal gridUnit) const
    function QColor (line 317) | QColor Button::backgroundColor() const
    function QColor (line 395) | QColor Button::foregroundColor() const
    function QRectF (line 462) | QRectF Button::contentArea() const
    function qreal (line 498) | qreal Button::opacity() const
    function qreal (line 511) | qreal Button::transitionValue() const
    function QMargins (line 524) | QMargins* Button::padding()

FILE: src/Button.h
  function namespace (line 30) | namespace Material

FILE: src/CloseButton.h
  function namespace (line 29) | namespace Material

FILE: src/ConfigurationModule.cc
  type Material (line 51) | namespace Material

FILE: src/ConfigurationModule.h
  function namespace (line 26) | namespace Material

FILE: src/ContextHelpButton.h
  function namespace (line 34) | namespace Material

FILE: src/Decoration.cc
  type Material (line 60) | namespace Material
    type ShadowParams (line 66) | struct ShadowParams
      method ShadowParams (line 68) | ShadowParams() = default;
      method ShadowParams (line 70) | ShadowParams(const QPoint &offset, int radius, qreal opacity)
    type CompositeShadowParams (line 80) | struct CompositeShadowParams
      method CompositeShadowParams (line 82) | CompositeShadowParams() = default;
      method CompositeShadowParams (line 84) | CompositeShadowParams(
      method isNone (line 92) | bool isNone() const {
    function CompositeShadowParams (line 131) | inline CompositeShadowParams lookupShadowParams(int size)
      method CompositeShadowParams (line 82) | CompositeShadowParams() = default;
      method CompositeShadowParams (line 84) | CompositeShadowParams(
      method isNone (line 92) | bool isNone() const {
    function QRect (line 170) | QRect Decoration::titleBarRect() const
    function QRect (line 175) | QRect Decoration::centerRect() const
    function QPoint (line 774) | QPoint Decoration::windowPos() const
    function QColor (line 950) | QColor Decoration::borderColor() const
    function QColor (line 964) | QColor Decoration::titleBarBackgroundColor() const
    function QColor (line 978) | QColor Decoration::titleBarForegroundColor() const

FILE: src/Decoration.h
  function namespace (line 44) | namespace Material

FILE: src/KeepAboveButton.h
  function namespace (line 30) | namespace Material

FILE: src/KeepBelowButton.h
  function namespace (line 30) | namespace Material

FILE: src/Material.h
  function namespace (line 24) | namespace Material

FILE: src/MaximizeButton.h
  function namespace (line 29) | namespace Material

FILE: src/MenuOverflowButton.cc
  type Material (line 34) | namespace Material

FILE: src/MenuOverflowButton.h
  function namespace (line 24) | namespace Material

FILE: src/MinimizeButton.h
  function namespace (line 29) | namespace Material

FILE: src/OnAllDesktopsButton.h
  function namespace (line 30) | namespace Material

FILE: src/ShadeButton.h
  function namespace (line 30) | namespace Material

FILE: src/TextButton.cc
  type Material (line 37) | namespace Material
    function QSize (line 70) | QSize TextButton::getTextSize()
    function QAction (line 88) | QAction* TextButton::action() const
    function QString (line 101) | QString TextButton::text() const

FILE: src/TextButton.h
  function namespace (line 26) | namespace Material

FILE: src/libdbusmenuqt/dbusmenuimporter.cpp
  function QAction (line 64) | static QAction *createKdeTitle(QAction *action, QWidget *parent)
  class DBusMenuImporterPrivate (line 80) | class DBusMenuImporterPrivate
    method QDBusPendingCallWatcher (line 94) | QDBusPendingCallWatcher *refresh(int id)
    method QMenu (line 104) | QMenu *createMenu(QWidget *parent)
    method QAction (line 118) | QAction *createAction(int id, const QVariantMap &_map, QWidget *parent)
    method updateAction (line 162) | void updateAction(QAction *action, const QVariantMap &map, const QStri...
    method updateActionProperty (line 169) | void updateActionProperty(QAction *action, const QString &key, const Q...
    method updateActionLabel (line 190) | void updateActionLabel(QAction *action, const QVariant &value)
    method updateActionEnabled (line 196) | void updateActionEnabled(QAction *action, const QVariant &value)
    method updateActionChecked (line 201) | void updateActionChecked(QAction *action, const QVariant &value)
    method updateActionIconByName (line 208) | void updateActionIconByName(QAction *action, const QVariant &value)
    method updateActionIconByData (line 223) | void updateActionIconByData(QAction *action, const QVariant &value)
    method updateActionVisible (line 241) | void updateActionVisible(QAction *action, const QVariant &value)
    method updateActionShortcut (line 246) | void updateActionShortcut(QAction *action, const QVariant &value)
    method QMenu (line 255) | QMenu *menuForId(int id) const
    method sendEvent (line 269) | void sendEvent(int id, const QString &eventId)
  function Q_FOREACH (line 326) | Q_FOREACH (int id, ids) {
  function QMenu (line 331) | QMenu *DBusMenuImporter::menu() const
  function Q_FOREACH (line 341) | Q_FOREACH (const DBusMenuItem &item, updatedList) {
  function Q_FOREACH (line 354) | Q_FOREACH (const DBusMenuItemKeys &item, removedList) {
  function QAction (line 367) | QAction *DBusMenuImporter::actionForId(int id) const
  function QMenu (line 540) | QMenu *DBusMenuImporter::createMenu(QWidget *parent)
  function QIcon (line 545) | QIcon DBusMenuImporter::iconForName(const QString & /*name*/)

FILE: src/libdbusmenuqt/dbusmenuimporter.h
  function class (line 38) | class DBusMenuImporter : public QObject

FILE: src/libdbusmenuqt/dbusmenushortcut_p.cpp
  function processKeyTokens (line 29) | static void processKeyTokens(QStringList *tokens, int srcCol, int dstCol)
  function DBusMenuShortcut (line 56) | DBusMenuShortcut DBusMenuShortcut::fromKeySequence(const QKeySequence &s...
  function QKeySequence (line 73) | QKeySequence DBusMenuShortcut::toKeySequence() const

FILE: src/libdbusmenuqt/dbusmenushortcut_p.h
  function class (line 30) | class DBusMenuShortcut : public QList<QStringList>

FILE: src/libdbusmenuqt/dbusmenutypes_p.cpp
  function QDBusArgument (line 31) | QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItem &obj)
  function QDBusArgument (line 39) | const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuI...
  function QDBusArgument (line 48) | QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItemKey...
  function QDBusArgument (line 56) | const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuI...
  function QDBusArgument (line 65) | QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuLayoutI...
  function QDBusArgument (line 78) | const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuL...
  function QDBusArgument (line 98) | QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuShortcu...
  function QDBusArgument (line 109) | const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuS...
  function DBusMenuTypes_register (line 122) | void DBusMenuTypes_register()

FILE: src/libdbusmenuqt/dbusmenutypes_p.h
  type DBusMenuItem (line 35) | struct DBusMenuItem {
  type QList (line 45) | typedef QList<DBusMenuItem> DBusMenuItemList;
  function DBusMenuItemKeys (line 47) | Q_DECLARE_METATYPE(DBusMenuItemList)
  type QList (line 63) | typedef QList<DBusMenuItemKeys> DBusMenuItemKeysList;
  type DBusMenuLayoutItem (line 73) | struct DBusMenuLayoutItem {
  type QList (line 84) | typedef QList<DBusMenuLayoutItem> DBusMenuLayoutItemList;

FILE: src/libdbusmenuqt/test/main.cpp
  class MainWindow (line 27) | class MainWindow : public QMainWindow
  function main (line 78) | int main(int argc, char **argv)

FILE: src/libdbusmenuqt/utils.cpp
  function QString (line 26) | QString swapMnemonicChar(const QString &in, const char src, const char dst)
Condensed preview — 55 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (236K chars).
[
  {
    "path": ".clang-format",
    "chars": 3269,
    "preview": "---\nLanguage:        Cpp\n# BasedOnStyle:  WebKit\nAccessModifierOffset: -4\nAlignAfterOpenBracket: DontAlign\nAlignConsecut"
  },
  {
    "path": ".gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 90,
    "preview": "{\n    \"files.associations\": {\n        \"array\": \"cpp\",\n        \"string_view\": \"cpp\"\n    }\n}"
  },
  {
    "path": "CMakeLists.txt",
    "chars": 932,
    "preview": "cmake_minimum_required (VERSION 3.16.0)\nproject (material-decoration)\n\nadd_definitions (-Wall -Werror)\n\ninclude (Feature"
  },
  {
    "path": "LICENSE",
    "chars": 18092,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Fr"
  },
  {
    "path": "README.md",
    "chars": 2023,
    "preview": "![Demo](data/preview.png)\n\n## material-decoration\n\nMaterial-ish window decoration theme for KWin.\n\n### Locally Integrate"
  },
  {
    "path": "src/AppIconButton.h",
    "chars": 2389,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n "
  },
  {
    "path": "src/AppMenuButton.cc",
    "chars": 2839,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2016 Kai Uwe Broulik <kde@privat.broulik.de"
  },
  {
    "path": "src/AppMenuButton.h",
    "chars": 1322,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n *\n * This program is free software: you can redistribute it"
  },
  {
    "path": "src/AppMenuButtonGroup.cc",
    "chars": 18406,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2016 Kai Uwe Broulik <kde@privat.broulik.de"
  },
  {
    "path": "src/AppMenuButtonGroup.h",
    "chars": 3721,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n *\n * This program is free software: you can redistribute it"
  },
  {
    "path": "src/AppMenuModel.cc",
    "chars": 13848,
    "preview": "/******************************************************************\n * Copyright 2016 Kai Uwe Broulik <kde@privat.brouli"
  },
  {
    "path": "src/AppMenuModel.h",
    "chars": 3130,
    "preview": "/******************************************************************\n * Copyright 2016 Chinmoy Ranjan Pradhan <chinmoyrp6"
  },
  {
    "path": "src/ApplicationMenuButton.h",
    "chars": 1599,
    "preview": "/*\n * Copyright (C) 2019 Zain Ahmad <zain.x.ahmad@gmail.com>\n *\n * This program is free software: you can redistribute i"
  },
  {
    "path": "src/BoxShadowHelper.cc",
    "chars": 5404,
    "preview": "/*\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n *\n * This program is free software: you can redistribute "
  },
  {
    "path": "src/BoxShadowHelper.h",
    "chars": 1032,
    "preview": "/*\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n *\n * This program is free software: you can redistribute "
  },
  {
    "path": "src/BuildConfig.h.cmake",
    "chars": 118,
    "preview": "#cmakedefine01 HAVE_Wayland\n#cmakedefine01 HAVE_X11\n#cmakedefine01 HAVE_KDecoration2_5_25\n#cmakedefine01 HAVE_KF5_101\n"
  },
  {
    "path": "src/Button.cc",
    "chars": 17157,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n "
  },
  {
    "path": "src/Button.h",
    "chars": 3519,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n "
  },
  {
    "path": "src/CMakeLists.txt",
    "chars": 3248,
    "preview": "find_package (KDecoration2 REQUIRED)\n\nfind_package (Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS\n    Core\n    Gui\n)\n\nfind_p"
  },
  {
    "path": "src/CloseButton.h",
    "chars": 1640,
    "preview": "/*\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n *\n * This program is free software: you can redistribute "
  },
  {
    "path": "src/ConfigurationModule.cc",
    "chars": 10501,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2012 Martin Gräßlin <mgraesslin@kde.org>\n *"
  },
  {
    "path": "src/ConfigurationModule.h",
    "chars": 1355,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2012 Martin Gräßlin <mgraesslin@kde.org>\n *"
  },
  {
    "path": "src/ContextHelpButton.h",
    "chars": 2306,
    "preview": "/*\n * Copyright (C) 2021 Chris Holland <zrenfire@gmail.com>\n *\n * This program is free software: you can redistribute it"
  },
  {
    "path": "src/Decoration.cc",
    "chars": 36445,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n "
  },
  {
    "path": "src/Decoration.h",
    "chars": 4616,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n "
  },
  {
    "path": "src/InternalSettings.kcfgc",
    "chars": 126,
    "preview": "File=InternalSettingsSchema.kcfg\nClassName=InternalSettings\nNameSpace=Material\nSingleton=false\nMutators=true\nGlobalEnums"
  },
  {
    "path": "src/InternalSettingsSchema.kcfg",
    "chars": 2578,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kcfg xmlns=\"http://www.kde.org/standards/kcfg/1.0\" xmlns:xsi=\"http://www.w3.org/"
  },
  {
    "path": "src/KeepAboveButton.h",
    "chars": 1721,
    "preview": "/*\n * Copyright (C) 2019 Zain Ahmad <zain.x.ahmad@gmail.com>\n *\n * This program is free software: you can redistribute i"
  },
  {
    "path": "src/KeepBelowButton.h",
    "chars": 1720,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n *\n * This program is free software: you can redistribute it"
  },
  {
    "path": "src/Material.h",
    "chars": 1414,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n *\n * This program is free software: you can redistribute it"
  },
  {
    "path": "src/MaximizeButton.h",
    "chars": 2283,
    "preview": "/*\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n *\n * This program is free software: you can redistribute "
  },
  {
    "path": "src/MenuOverflowButton.cc",
    "chars": 1580,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2019 Zain Ahmad <zain.x.ahmad@gmail.com>\n *"
  },
  {
    "path": "src/MenuOverflowButton.h",
    "chars": 1190,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2019 Zain Ahmad <zain.x.ahmad@gmail.com>\n *"
  },
  {
    "path": "src/MinimizeButton.h",
    "chars": 1521,
    "preview": "/*\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n *\n * This program is free software: you can redistribute "
  },
  {
    "path": "src/OnAllDesktopsButton.h",
    "chars": 1711,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n *\n * This program is free software: you can redistribute it"
  },
  {
    "path": "src/ShadeButton.h",
    "chars": 2392,
    "preview": "/*\n * Copyright (C) 2021 Chris Holland <zrenfire@gmail.com>\n *\n * This program is free software: you can redistribute it"
  },
  {
    "path": "src/TextButton.cc",
    "chars": 3189,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n * Copyright (C) 2016 Kai Uwe Broulik <kde@privat.broulik.de"
  },
  {
    "path": "src/TextButton.h",
    "chars": 1630,
    "preview": "/*\n * Copyright (C) 2020 Chris Holland <zrenfire@gmail.com>\n *\n * This program is free software: you can redistribute it"
  },
  {
    "path": "src/libdbusmenuqt/CMakeLists.txt",
    "chars": 834,
    "preview": "set(libdbusmenu_SRCS\n    dbusmenuimporter.cpp\n    dbusmenushortcut_p.cpp\n    dbusmenutypes_p.cpp\n    utils.cpp\n)\n\necm_qt"
  },
  {
    "path": "src/libdbusmenuqt/README",
    "chars": 119,
    "preview": "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",
    "chars": 2169,
    "preview": "<interface name=\"com.canonical.dbusmenu\">\n    <property name=\"Version\" type=\"u\" access=\"read\"/>\n    <property name=\"Stat"
  },
  {
    "path": "src/libdbusmenuqt/dbusmenuimporter.cpp",
    "chars": 18319,
    "preview": "/* This file is part of the dbusmenu-qt library\n   Copyright 2009 Canonical\n   Author: Aurelien Gateau <aurelien.gateau@"
  },
  {
    "path": "src/libdbusmenuqt/dbusmenuimporter.h",
    "chars": 3228,
    "preview": "/* This file is part of the dbusmenu-qt library\n   Copyright 2009 Canonical\n   Author: Aurelien Gateau <aurelien.gateau@"
  },
  {
    "path": "src/libdbusmenuqt/dbusmenushortcut_p.cpp",
    "chars": 3139,
    "preview": "/* This file is part of the dbusmenu-qt library\n   Copyright 2009 Canonical\n   Author: Aurelien Gateau <aurelien.gateau@"
  },
  {
    "path": "src/libdbusmenuqt/dbusmenushortcut_p.h",
    "chars": 1286,
    "preview": "/* This file is part of the dbusmenu-qt library\n   Copyright 2009 Canonical\n   Author: Aurelien Gateau <aurelien.gateau@"
  },
  {
    "path": "src/libdbusmenuqt/dbusmenutypes_p.cpp",
    "chars": 4119,
    "preview": "/* This file is part of the dbusmenu-qt library\n   Copyright 2009 Canonical\n   Author: Aurelien Gateau <aurelien.gateau@"
  },
  {
    "path": "src/libdbusmenuqt/dbusmenutypes_p.h",
    "chars": 2767,
    "preview": "/* This file is part of the dbusmenu-qt library\n   Copyright 2009 Canonical\n   Author: Aurelien Gateau <aurelien.gateau@"
  },
  {
    "path": "src/libdbusmenuqt/test/CMakeLists.txt",
    "chars": 108,
    "preview": "add_executable(appmenutest main.cpp)\ntarget_link_libraries(appmenutest\n    Qt${QT_VERSION_MAJOR}::Widgets\n)\n"
  },
  {
    "path": "src/libdbusmenuqt/test/README",
    "chars": 159,
    "preview": "App with a menu, designed for use testing appmenu QPTs/applets/kded modules\nsmall enough that we can attach debuggers an"
  },
  {
    "path": "src/libdbusmenuqt/test/main.cpp",
    "chars": 2133,
    "preview": "/*\n *   Copyright 2017 David Edmundson <davidedmundson@kde.org>\n *   This program is free software; you can redistribute"
  },
  {
    "path": "src/libdbusmenuqt/utils.cpp",
    "chars": 2037,
    "preview": "/* This file is part of the dbusmenu-qt library\n   Copyright 2010 Canonical\n   Author: Aurelien Gateau <aurelien.gateau@"
  },
  {
    "path": "src/libdbusmenuqt/utils_p.h",
    "chars": 1139,
    "preview": "/* This file is part of the dbusmenu-qt library\n   Copyright 2010 Canonical\n   Author: Aurelien Gateau <aurelien.gateau@"
  },
  {
    "path": "src/material.json",
    "chars": 372,
    "preview": "{\n    \"KPlugin\": {\n        \"Description\": \"Window decoration\",\n        \"EnabledByDefault\": false,\n        \"Id\": \"com.git"
  },
  {
    "path": "src/plugin.cc",
    "chars": 1076,
    "preview": "/*\n * Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>\n *\n * This program is free software: you can redistribute "
  }
]

About this extraction

This page contains the full source code of the Zren/material-decoration GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 55 files (219.8 KB), approximately 55.2k tokens, and a symbol index with 115 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!